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:
4
internal/app/api/wwapic/README.md
Normal file
4
internal/app/api/wwapic/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# wwapiclient is intended as and example grpc wwapid client.
|
||||
Run wwapid to start the server
|
||||
```./wwapic``` will get the version.
|
||||
This works with or without mTLS.
|
||||
89
internal/app/api/wwapic/wwapic.go
Normal file
89
internal/app/api/wwapic/wwapic.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/apiconfig"
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
|
||||
"github.com/hpcng/warewulf/internal/pkg/buildconfig"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
// wwapic is intended as a sample wwapi client.
|
||||
|
||||
func main() {
|
||||
log.Println("Client running")
|
||||
|
||||
// Read the config file.
|
||||
config, err := apiconfig.NewClient(path.Join(buildconfig.SYSCONFDIR(), "warewulf/wwapic.conf"))
|
||||
if err != nil {
|
||||
log.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
var opts []grpc.DialOption
|
||||
if config.TlsConfig.Enabled {
|
||||
|
||||
// Load the client cert and its key
|
||||
clientCert, err := tls.LoadX509KeyPair(config.TlsConfig.Cert, config.TlsConfig.Key)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load client cert and key. %s.", err)
|
||||
}
|
||||
|
||||
// Load the CA cert.
|
||||
var cacert []byte
|
||||
cacert, err = ioutil.ReadFile(config.TlsConfig.CaCert)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load cacert. err: %s\n", err)
|
||||
}
|
||||
|
||||
// Put the CA cert into the cert pool.
|
||||
certPool := x509.NewCertPool()
|
||||
if !certPool.AppendCertsFromPEM(cacert) {
|
||||
log.Fatalf("Failed to append CA cert to certificate pool. %s.", err)
|
||||
}
|
||||
|
||||
// Create the TLS configuration
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{clientCert},
|
||||
RootCAs: certPool,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
MaxVersion: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
// Create TLS credentials from the TLS configuration
|
||||
creds := credentials.NewTLS(tlsConfig)
|
||||
opts = append(opts, grpc.DialOption(grpc.WithTransportCredentials(creds)))
|
||||
} else {
|
||||
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
}
|
||||
|
||||
conn, err := grpc.Dial(fmt.Sprintf("%s:%v", config.ApiConfig.Server, config.ApiConfig.Port), opts...)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := wwapiv1.NewWWApiClient(conn)
|
||||
request := &emptypb.Empty{}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
response, err := client.Version(ctx, request)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
log.Printf("Version Response: %v\n", response)
|
||||
}
|
||||
7
internal/app/api/wwapid/README.md
Normal file
7
internal/app/api/wwapid/README.md
Normal file
@@ -0,0 +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```.
|
||||
|
||||
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.
|
||||
343
internal/app/api/wwapid/wwapid.go
Normal file
343
internal/app/api/wwapid/wwapid.go
Normal file
@@ -0,0 +1,343 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/apiconfig"
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/container"
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/node"
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
|
||||
"github.com/hpcng/warewulf/internal/pkg/buildconfig"
|
||||
"github.com/hpcng/warewulf/internal/pkg/version"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
type apiServer struct {
|
||||
wwapiv1.UnimplementedWWApiServer
|
||||
}
|
||||
|
||||
var apiPrefix string
|
||||
var apiVersion string
|
||||
|
||||
func main() {
|
||||
log.Println("Server running")
|
||||
|
||||
// Read the config file.
|
||||
config, err := apiconfig.NewServer(path.Join(buildconfig.SYSCONFDIR(), "warewulf/wwapid.conf"))
|
||||
if err != nil {
|
||||
log.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Pull out config variables.
|
||||
apiPrefix = config.ApiConfig.Prefix
|
||||
apiVersion = config.ApiConfig.Version
|
||||
servicePort := config.ApiConfig.Port
|
||||
portString := fmt.Sprintf(":%d", servicePort)
|
||||
|
||||
var opts []grpc.ServerOption
|
||||
if !config.TlsConfig.Enabled {
|
||||
insecureMode()
|
||||
} else {
|
||||
|
||||
// Setup TLS.
|
||||
serverCert, err := tls.LoadX509KeyPair(config.TlsConfig.Cert, config.TlsConfig.Key)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load server cert and key. err: %s\n", err)
|
||||
}
|
||||
|
||||
// Load the CA cert.
|
||||
var cacert []byte
|
||||
cacert, err = ioutil.ReadFile(config.TlsConfig.CaCert)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load cacert. err: %s\n", err)
|
||||
}
|
||||
|
||||
// Put the CA cert into the cert pool.
|
||||
certPool := x509.NewCertPool()
|
||||
if !certPool.AppendCertsFromPEM(cacert) {
|
||||
log.Fatalf("Failed to append CA cert to certificate pool. %s.", err)
|
||||
}
|
||||
|
||||
// Create the TLS configuration
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{serverCert},
|
||||
RootCAs: certPool,
|
||||
ClientCAs: certPool,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
MaxVersion: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
// Create TLS credentials from the TLS configuration
|
||||
creds := credentials.NewTLS(tlsConfig)
|
||||
opts = []grpc.ServerOption{grpc.Creds(creds)}
|
||||
}
|
||||
|
||||
listen, err := net.Listen("tcp", portString)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer func() {
|
||||
listen.Close()
|
||||
}()
|
||||
|
||||
grpcServer := grpc.NewServer(opts...)
|
||||
wwapiv1.RegisterWWApiServer(grpcServer, &apiServer{})
|
||||
log.Fatalln(grpcServer.Serve(listen))
|
||||
}
|
||||
|
||||
// private helpers
|
||||
|
||||
// insecureMode creates a blocking prompt for customers running wwapid in insecure mode.
|
||||
// It's a deterrent. Setup TLS.
|
||||
func insecureMode() {
|
||||
|
||||
fmt.Println("*** Running wwapid in INSECURE mode. *** THIS IS DANGEROUS! *** Enter y to continue. ***")
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
result, err := reader.ReadString('\n')
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Fatal error: %v\n", err)
|
||||
}
|
||||
|
||||
if !(result == "y\n") {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("wwapid running IN INSECURE MODE\n")
|
||||
}
|
||||
|
||||
// Api implementation.
|
||||
|
||||
// ContainerBuild builds one or more containers.
|
||||
func (s *apiServer) ContainerBuild(ctx context.Context, request *wwapiv1.ContainerBuildParameter) (response *wwapiv1.ContainerListResponse, 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")
|
||||
}
|
||||
|
||||
// Build the container.
|
||||
err = container.ContainerBuild(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Return the built containers. (A REST POST returns what is modified.)
|
||||
var containers []*wwapiv1.ContainerInfo
|
||||
containers, err = container.ContainerList()
|
||||
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])
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ContainerDelete deletes one or more containers from Warewulf.
|
||||
func (s *apiServer) ContainerDelete(ctx context.Context, request *wwapiv1.ContainerDeleteParameter) (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")
|
||||
}
|
||||
|
||||
err = container.ContainerDelete(request)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *apiServer) ContainerImport(ctx context.Context, request *wwapiv1.ContainerImportParameter) (response *wwapiv1.ContainerListResponse, err error) {
|
||||
|
||||
// Import the container.
|
||||
var containerName string
|
||||
containerName, err = container.ContainerImport(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Return the imported container to the client.
|
||||
var containers []*wwapiv1.ContainerInfo
|
||||
containers, err = container.ContainerList()
|
||||
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]},
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ContainerList returns details about containers.
|
||||
func (s *apiServer) ContainerList(ctx context.Context, request *emptypb.Empty) (response *wwapiv1.ContainerListResponse, err error) {
|
||||
|
||||
var containers []*wwapiv1.ContainerInfo
|
||||
containers, err = container.ContainerList()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
response = &wwapiv1.ContainerListResponse{
|
||||
Containers: containers,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ContainerShow returns details about containers.
|
||||
func (s *apiServer) ContainerShow(ctx context.Context, request *wwapiv1.ContainerShowParameter) (response *wwapiv1.ContainerShowResponse, err error) {
|
||||
|
||||
// Parameter checks.
|
||||
if request == nil {
|
||||
return response, status.Errorf(codes.InvalidArgument, "nil request")
|
||||
}
|
||||
|
||||
return container.ContainerShow(request)
|
||||
}
|
||||
|
||||
// NodeAdd adds one or more nodes for management by Warewulf and returns the added nodes.
|
||||
func (s *apiServer) NodeAdd(ctx context.Context, request *wwapiv1.NodeAddParameter) (response *wwapiv1.NodeListResponse, err error) {
|
||||
|
||||
// Parameter checks.
|
||||
if request == nil {
|
||||
return response, status.Errorf(codes.InvalidArgument, "nil request")
|
||||
}
|
||||
|
||||
if request.NodeNames == nil {
|
||||
return response, status.Errorf(codes.InvalidArgument, "nil request.NodeNames")
|
||||
}
|
||||
|
||||
// Add the node(s).
|
||||
err = node.NodeAdd(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Return the added nodes as per REST.
|
||||
return s.nodeListInternal(request.NodeNames)
|
||||
}
|
||||
|
||||
// NodeDelete deletes one or more nodes for removal of management by Warewulf.
|
||||
func (s *apiServer) NodeDelete(ctx context.Context, request *wwapiv1.NodeDeleteParameter) (response *emptypb.Empty, err error) {
|
||||
|
||||
response = new(emptypb.Empty)
|
||||
|
||||
// Parameter checks.
|
||||
if request == nil {
|
||||
return response, status.Errorf(codes.InvalidArgument, "nil request")
|
||||
}
|
||||
|
||||
if request.NodeNames == nil {
|
||||
return response, status.Errorf(codes.InvalidArgument, "nil request.NodeNames")
|
||||
}
|
||||
|
||||
err = node.NodeDelete(request)
|
||||
return
|
||||
}
|
||||
|
||||
// NodeList returns details about zero or more nodes.
|
||||
func (s *apiServer) NodeList(ctx context.Context, request *wwapiv1.NodeNames) (response *wwapiv1.NodeListResponse, err error) {
|
||||
|
||||
// Parameter checks. request.NodeNames can be nil.
|
||||
if request == nil {
|
||||
return response, status.Errorf(codes.InvalidArgument, "nil request")
|
||||
}
|
||||
|
||||
// Perform the list.
|
||||
return s.nodeListInternal(request.NodeNames)
|
||||
}
|
||||
|
||||
// NodeSet updates fields for zero or more nodes and returns the updated nodes.
|
||||
func (s *apiServer) NodeSet(ctx context.Context, request *wwapiv1.NodeSetParameter) (response *wwapiv1.NodeListResponse, err error) {
|
||||
|
||||
// Parameter checks.
|
||||
if request == nil {
|
||||
return response, status.Errorf(codes.InvalidArgument, "nil request")
|
||||
}
|
||||
|
||||
if request.NodeNames == nil {
|
||||
return response, status.Errorf(codes.InvalidArgument, "nil request.NodeNames")
|
||||
}
|
||||
|
||||
// Perform the NodeSet.
|
||||
err = node.NodeSet(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Return the updated nodes as per REST.
|
||||
return s.nodeListInternal(request.NodeNames)
|
||||
}
|
||||
|
||||
func (s *apiServer) NodeStatus(ctx context.Context, request *wwapiv1.NodeNames) (response *wwapiv1.NodeStatusResponse, err error) {
|
||||
|
||||
// Parameter checks. request.NodeNames can be nil.
|
||||
if request == nil {
|
||||
return response, status.Errorf(codes.InvalidArgument, "nil request")
|
||||
}
|
||||
|
||||
return node.NodeStatus(request.NodeNames)
|
||||
}
|
||||
|
||||
// Version returns the versions of the wwapiv1 and warewulf as well as the api
|
||||
// prefix for http routes.
|
||||
func (s *apiServer) Version(ctx context.Context, request *emptypb.Empty) (response *wwapiv1.VersionResponse, err error) {
|
||||
|
||||
response = &wwapiv1.VersionResponse{
|
||||
ApiPrefix: apiPrefix,
|
||||
ApiVersion: apiVersion,
|
||||
WarewulfVersion: version.GetVersion(),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Private helpers.
|
||||
|
||||
// nodeListInternal calls NodeList and returns NodeListResponse.
|
||||
// This does not contain parameter checks.
|
||||
func (s *apiServer) nodeListInternal(nodeNames []string) (response *wwapiv1.NodeListResponse, err error) {
|
||||
|
||||
var nodes []*wwapiv1.NodeInfo
|
||||
nodes, err = node.NodeList(nodeNames)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
response = &wwapiv1.NodeListResponse{
|
||||
Nodes: nodes,
|
||||
}
|
||||
return
|
||||
}
|
||||
9
internal/app/api/wwapird/README.md
Normal file
9
internal/app/api/wwapird/README.md
Normal file
@@ -0,0 +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```.
|
||||
|
||||
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.
|
||||
112
internal/app/api/wwapird/wwapird.go
Normal file
112
internal/app/api/wwapird/wwapird.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/apiconfig"
|
||||
|
||||
gw "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
|
||||
|
||||
"github.com/hpcng/warewulf/internal/pkg/buildconfig"
|
||||
"path"
|
||||
)
|
||||
|
||||
func run() error {
|
||||
|
||||
log.Println("test0")
|
||||
|
||||
// Read the config file.
|
||||
config, err := apiconfig.NewClientServer(path.Join(buildconfig.SYSCONFDIR(), "warewulf/wwapird.conf"))
|
||||
if err != nil {
|
||||
glog.Fatalf("Failed to read config file, err: %v", err)
|
||||
}
|
||||
grpcServerEndpoint := fmt.Sprintf("%s:%v", config.ClientApiConfig.Server, config.ClientApiConfig.Port)
|
||||
httpServerEndpoint := fmt.Sprintf(":%v", config.ServerApiConfig.Port)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
// Register gRPC server endpoint (we are the client)
|
||||
// Note: Make sure the gRPC server is running properly and accessible
|
||||
mux := runtime.NewServeMux()
|
||||
|
||||
var opts []grpc.DialOption
|
||||
if config.ClientTlsConfig.Enabled {
|
||||
|
||||
// Load the client cert and its key
|
||||
clientCert, err := tls.LoadX509KeyPair(config.ClientTlsConfig.Cert, config.ClientTlsConfig.Key)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load client cert and key. %s.", err)
|
||||
}
|
||||
|
||||
// Load the CA cert.
|
||||
var cacert []byte
|
||||
cacert, err = ioutil.ReadFile(config.ClientTlsConfig.CaCert)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load cacert. err: %s\n", err)
|
||||
}
|
||||
|
||||
// Put the CA cert into the cert pool.
|
||||
certPool := x509.NewCertPool()
|
||||
if !certPool.AppendCertsFromPEM(cacert) {
|
||||
log.Fatalf("Failed to append CA cert to certificate pool. %s.", err)
|
||||
}
|
||||
|
||||
// Create the TLS configuration
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{clientCert},
|
||||
RootCAs: certPool,
|
||||
MinVersion: tls.VersionTLS13,
|
||||
MaxVersion: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
// Create TLS credentials from the TLS configuration
|
||||
creds := credentials.NewTLS(tlsConfig)
|
||||
opts = append(opts, grpc.DialOption(grpc.WithTransportCredentials(creds)))
|
||||
|
||||
} else {
|
||||
opts = []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
|
||||
}
|
||||
|
||||
err = gw.RegisterWWApiHandlerFromEndpoint(ctx, mux, grpcServerEndpoint, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Start HTTP server (and proxy calls to gRPC server endpoint)
|
||||
if config.ServerTlsConfig.Enabled {
|
||||
|
||||
return http.ListenAndServeTLS(
|
||||
httpServerEndpoint,
|
||||
config.ServerTlsConfig.ConcatCert,
|
||||
config.ServerTlsConfig.Key,
|
||||
mux)
|
||||
}
|
||||
|
||||
// Insecure
|
||||
return http.ListenAndServe(httpServerEndpoint, mux)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse() // Pretty sure glog wants this.
|
||||
defer glog.Flush()
|
||||
|
||||
if err := run(); err != nil {
|
||||
glog.Fatal(err)
|
||||
}
|
||||
}
|
||||
57
internal/app/api/wwapird_curls/curls.sh
Normal file
57
internal/app/api/wwapird_curls/curls.sh
Normal file
@@ -0,0 +1,57 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
# This file is a scratchpad for curling wwapird.
|
||||
|
||||
# version
|
||||
curl http://localhost:9871/version
|
||||
|
||||
# secure version
|
||||
curl --cacert /usr/local/etc/warewulf/keys/cacert.pem \
|
||||
--key /usr/local/etc/warewulf/keys/client.key \
|
||||
--cert /usr/local/etc/warewulf/keys/client.pem \
|
||||
https://localhost:9871/version
|
||||
|
||||
# container list all
|
||||
curl http://localhost:9871/v1/container
|
||||
|
||||
# container import
|
||||
curl -d '{"source": "docker://warewulf/rocky:8", "name": "rocky-8", "update": true, "default": true}' -H "Content-Type: application/json" -X POST http://localhost:9871/v1/container
|
||||
|
||||
# container delete
|
||||
curl -X DELETE http://localhost:9871/v1/container?containerNames=rocky-8
|
||||
|
||||
# container build
|
||||
curl -d '{"containerNames": ["rocky-8"], "force": true}' -H "Content-Type: application/json" -X POST http://localhost:9871/v1/containerbuild
|
||||
|
||||
# node list all
|
||||
curl http://localhost:9871/v1/node
|
||||
|
||||
# node list one
|
||||
curl http://localhost:9871/v1/node?nodeNames=testnode1 # this works! case sensitive
|
||||
|
||||
# This is a list of testnode[1-2] with URL escapes.
|
||||
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": ["testApiNode1"], "discoverable": true}' -H "Content-Type: application/json" -X POST http://localhost:9871/v1/node
|
||||
|
||||
|
||||
# list the node we just added
|
||||
curl http://localhost:9871/v1/node?nodeNames=testApiNode0
|
||||
|
||||
# This gets me a little farther, but still no param data:
|
||||
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
|
||||
|
||||
# node status
|
||||
curl http://localhost:9871/v1/nodestatus
|
||||
|
||||
curl http://localhost:9871/v1/nodestatus?nodeNames=testApiNode0
|
||||
|
||||
# node delete single node
|
||||
curl -X DELETE http://localhost:9871/v1/node?nodeNames=testApiNode0
|
||||
curl -X DELETE http://localhost:9871/v1/node?nodeNames=testApiNode1
|
||||
@@ -1,73 +1,17 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/hpcng/warewulf/internal/pkg/container"
|
||||
"github.com/hpcng/warewulf/internal/pkg/node"
|
||||
"github.com/hpcng/warewulf/internal/pkg/wwlog"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/container"
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
var containers []string
|
||||
|
||||
if BuildAll {
|
||||
containers, _ = container.ListSources()
|
||||
} else {
|
||||
containers = args
|
||||
cbp := &wwapiv1.ContainerBuildParameter{
|
||||
ContainerNames: args,
|
||||
Force: BuildForce,
|
||||
All: BuildAll,
|
||||
Default: SetDefault,
|
||||
}
|
||||
|
||||
if len(containers) == 0 {
|
||||
fmt.Println(cmd.Help())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
for _, c := range containers {
|
||||
if !container.ValidSource(c) {
|
||||
wwlog.Printf(wwlog.ERROR, "VNFS name does not exist: %s\n", c)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err := container.Build(c, BuildForce)
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "Could not build container %s: %s\n", c, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if SetDefault {
|
||||
if len(containers) != 1 {
|
||||
wwlog.Printf(wwlog.ERROR, "Can only set default for one container\n")
|
||||
} else {
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "Could not open node configuration: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
//TODO: Don't loop through profiles, instead have a nodeDB function that goes directly to the map
|
||||
profiles, _ := nodeDB.FindAllProfiles()
|
||||
for _, profile := range profiles {
|
||||
wwlog.Printf(wwlog.DEBUG, "Looking for profile default: %s\n", profile.Id.Get())
|
||||
if profile.Id.Get() == "default" {
|
||||
wwlog.Printf(wwlog.DEBUG, "Found profile default, setting container name to: %s\n", containers[0])
|
||||
profile.ContainerName.Set(containers[0])
|
||||
err := nodeDB.ProfileUpdate(profile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to update node profile")
|
||||
}
|
||||
}
|
||||
}
|
||||
err = nodeDB.Persist()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to persist nodedb")
|
||||
}
|
||||
fmt.Printf("Set default profile to container: %s\n", containers[0])
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return container.ContainerBuild(cbp)
|
||||
}
|
||||
|
||||
@@ -1,45 +1,15 @@
|
||||
package delete
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/hpcng/warewulf/internal/pkg/container"
|
||||
"github.com/hpcng/warewulf/internal/pkg/node"
|
||||
"github.com/hpcng/warewulf/internal/pkg/wwlog"
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/container"
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
|
||||
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "Could not open nodeDB: %s\n", err)
|
||||
os.Exit(1)
|
||||
cdp := &wwapiv1.ContainerDeleteParameter{
|
||||
ContainerNames: args,
|
||||
}
|
||||
|
||||
nodes, _ := nodeDB.FindAllNodes()
|
||||
|
||||
ARG_LOOP:
|
||||
for _, arg := range args {
|
||||
for _, n := range nodes {
|
||||
if n.ContainerName.Get() == arg {
|
||||
wwlog.Printf(wwlog.ERROR, "Container is configured for nodes, skipping: %s\n", arg)
|
||||
continue ARG_LOOP
|
||||
}
|
||||
}
|
||||
|
||||
if !container.ValidSource(arg) {
|
||||
wwlog.Printf(wwlog.ERROR, "Container name is not a valid source: %s\n", arg)
|
||||
continue
|
||||
}
|
||||
err := container.DeleteSource(arg)
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "Could not remove source: %s\n", arg)
|
||||
} else {
|
||||
fmt.Printf("Container has been deleted: %s\n", arg)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return container.ContainerDelete(cdp)
|
||||
}
|
||||
|
||||
@@ -1,179 +1,28 @@
|
||||
package imprt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/hpcng/warewulf/internal/pkg/container"
|
||||
"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/pkg/errors"
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/container"
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func setOCICredentials(sCtx *types.SystemContext) error {
|
||||
username, userSet := os.LookupEnv("WAREWULF_OCI_USERNAME")
|
||||
password, passSet := os.LookupEnv("WAREWULF_OCI_PASSWORD")
|
||||
if userSet || passSet {
|
||||
if userSet && passSet {
|
||||
sCtx.DockerAuthConfig = &types.DockerAuthConfig{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("oci username and password env vars must be specified together")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setNoHTTPSOpts(sCtx *types.SystemContext) error {
|
||||
val, ok := os.LookupEnv("WAREWULF_OCI_NOHTTPS")
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
noHTTPS, err := strconv.ParseBool(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("while parsing insecure http option: %v", err)
|
||||
}
|
||||
|
||||
// only set this if we want to disable, otherwise leave as undefined
|
||||
if noHTTPS {
|
||||
sCtx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(true)
|
||||
}
|
||||
sCtx.OCIInsecureSkipTLSVerify = noHTTPS
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSystemContext() (sCtx *types.SystemContext, err error) {
|
||||
sCtx = &types.SystemContext{}
|
||||
|
||||
if err := setOCICredentials(sCtx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := setNoHTTPSOpts(sCtx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sCtx, nil
|
||||
}
|
||||
|
||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
var name string
|
||||
uri := args[0]
|
||||
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
|
||||
|
||||
// Shim in a name if none given.
|
||||
name := ""
|
||||
if len(args) == 2 {
|
||||
name = args[1]
|
||||
} else {
|
||||
name = path.Base(uri)
|
||||
wwlog.Info("Setting VNFS name: %s", name)
|
||||
}
|
||||
|
||||
if !container.ValidName(name) {
|
||||
wwlog.Error("VNFS name contains illegal characters: %s", name)
|
||||
os.Exit(1)
|
||||
cip := &wwapiv1.ContainerImportParameter{
|
||||
Source: args[0],
|
||||
Name: name,
|
||||
Force: SetForce,
|
||||
Update: SetUpdate,
|
||||
Build: SetBuild,
|
||||
Default: SetDefault,
|
||||
SyncUser: SyncUser,
|
||||
}
|
||||
|
||||
fullPath := container.SourceDir(name)
|
||||
|
||||
if util.IsDir(fullPath) {
|
||||
if SetForce {
|
||||
wwlog.Info("Overwriting existing VNFS")
|
||||
err := os.RemoveAll(fullPath)
|
||||
if err != nil {
|
||||
wwlog.ErrorExc(err, "")
|
||||
os.Exit(1)
|
||||
}
|
||||
} else if SetUpdate {
|
||||
wwlog.Info("Updating existing VNFS")
|
||||
} else {
|
||||
wwlog.Error("VNFS Name exists, specify --force, --update, or choose a different name: %s", name)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else if strings.HasPrefix(uri, "docker://") || strings.HasPrefix(uri, "docker-daemon://") ||
|
||||
strings.HasPrefix(uri, "file://") || util.IsFile(uri) {
|
||||
sCtx, err := getSystemContext()
|
||||
if err != nil {
|
||||
wwlog.ErrorExc(err, "")
|
||||
}
|
||||
|
||||
err = container.ImportDocker(uri, name, sCtx)
|
||||
if err != nil {
|
||||
wwlog.Error("Could not import image: %s", err)
|
||||
_ = container.DeleteSource(name)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else if util.IsDir(uri) {
|
||||
err := container.ImportDirectory(uri, name)
|
||||
if err != nil {
|
||||
wwlog.Error("Could not import image: %s", err)
|
||||
_ = container.DeleteSource(name)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
wwlog.Error("Invalid dir or uri: %s", uri)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
wwlog.Info("Updating the container's /etc/resolv.conf")
|
||||
err := util.CopyFile("/etc/resolv.conf", path.Join(container.RootFsDir(name), "/etc/resolv.conf"))
|
||||
if err != nil {
|
||||
wwlog.Warn("Could not copy /etc/resolv.conf into container: %s", err)
|
||||
}
|
||||
|
||||
err = container.SyncUids(name, !SyncUser)
|
||||
if err != nil && !SyncUser {
|
||||
wwlog.Error("Error in user sync, fix error and run 'syncuser' manually: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
wwlog.Info("Building container: %s", name)
|
||||
err = container.Build(name, true)
|
||||
if err != nil {
|
||||
wwlog.Error("Could not build container %s: %s", name, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if SetDefault {
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
wwlog.Error("Could not open node configuration: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
//TODO: Don't loop through profiles, instead have a nodeDB function that goes directly to the map
|
||||
profiles, _ := nodeDB.FindAllProfiles()
|
||||
for _, profile := range profiles {
|
||||
wwlog.Printf(wwlog.DEBUG, "Looking for profile default: %s", profile.Id.Get())
|
||||
if profile.Id.Get() == "default" {
|
||||
wwlog.Printf(wwlog.DEBUG, "Found profile default, setting container name to: %s", name)
|
||||
profile.ContainerName.Set(name)
|
||||
err := nodeDB.ProfileUpdate(profile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to update profile")
|
||||
}
|
||||
}
|
||||
}
|
||||
err = nodeDB.Persist()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to persist nodedb")
|
||||
}
|
||||
|
||||
wwlog.Info("Set default profile to container: %s", name)
|
||||
err = warewulfd.DaemonReload()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to reload warewulf daemon")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
_, err = container.ContainerImport(cip)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,40 +2,26 @@ package list
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/hpcng/warewulf/internal/pkg/container"
|
||||
"github.com/hpcng/warewulf/internal/pkg/node"
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/container"
|
||||
"github.com/hpcng/warewulf/internal/pkg/wwlog"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
|
||||
|
||||
sources, err := container.ListSources()
|
||||
containerInfo, err := container.ContainerList()
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "%s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
nodeDB, _ := node.New()
|
||||
nodes, _ := nodeDB.FindAllNodes()
|
||||
nodemap := make(map[string]int)
|
||||
|
||||
for _, n := range nodes {
|
||||
nodemap[n.ContainerName.Get()]++
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%-25s %-6s %-6s\n", "CONTAINER NAME", "NODES", "KERNEL VERSION")
|
||||
for _, source := range sources {
|
||||
if nodemap[source] == 0 {
|
||||
nodemap[source] = 0
|
||||
}
|
||||
|
||||
wwlog.Printf(wwlog.DEBUG, "Finding kernel version for: %s\n", source)
|
||||
kernelVersion := container.KernelVersion(source)
|
||||
fmt.Printf("%-25s %-6d %s\n", source, nodemap[source], kernelVersion)
|
||||
|
||||
for i := 0; i < len(containerInfo); i++ {
|
||||
fmt.Printf("%-25s %-6d %-6s\n",
|
||||
containerInfo[i].Name,
|
||||
containerInfo[i].NodeCount,
|
||||
containerInfo[i].KernelVersion)
|
||||
}
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,39 +3,31 @@ package show
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hpcng/warewulf/internal/pkg/container"
|
||||
"github.com/hpcng/warewulf/internal/pkg/node"
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/container"
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
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)
|
||||
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
|
||||
|
||||
csp := &wwapiv1.ContainerShowParameter{
|
||||
ContainerName: args[0],
|
||||
}
|
||||
|
||||
var r *wwapiv1.ContainerShowResponse
|
||||
r, err = container.ContainerShow(csp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !ShowAll {
|
||||
fmt.Printf("%s\n", container.RootFsDir(containerName))
|
||||
fmt.Printf("%s\n", r.Rootfs)
|
||||
} else {
|
||||
fmt.Printf("Name: %s\n", containerName)
|
||||
fmt.Printf("Rootfs: %s\n", container.RootFsDir(containerName))
|
||||
kernelVersion := container.KernelVersion(containerName)
|
||||
if kernelVersion != "" {
|
||||
kernelVersion = "not found"
|
||||
fmt.Printf("Kernelversion: %s\n", kernelVersion)
|
||||
}
|
||||
nodeDB, _ := node.New()
|
||||
|
||||
nodes, _ := nodeDB.FindAllNodes()
|
||||
var nodeList []string
|
||||
for _, n := range nodes {
|
||||
if n.ContainerName.Get() == containerName {
|
||||
|
||||
nodeList = append(nodeList, n.Id.Get())
|
||||
}
|
||||
}
|
||||
fmt.Printf("Nr nodes: %d\n", len(nodeList))
|
||||
fmt.Printf("Nodes: %s\n", nodeList)
|
||||
|
||||
fmt.Printf("Name: %s\n", r.Name)
|
||||
fmt.Printf("Rootfs: %s\n", r.Rootfs)
|
||||
fmt.Printf("Nr nodes: %d\n", len(r.Nodes))
|
||||
fmt.Printf("Nodes: %s\n", r.Nodes)
|
||||
}
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,166 +1,24 @@
|
||||
package add
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"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/hpcng/warewulf/pkg/hostlist"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/node"
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
var count uint
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to open node database")
|
||||
|
||||
nap := wwapiv1.NodeAddParameter{
|
||||
Cluster: SetClusterName,
|
||||
Discoverable: SetDiscoverable,
|
||||
Gateway: SetGateway,
|
||||
Hwaddr: SetHwaddr,
|
||||
Ipaddr: SetIpaddr,
|
||||
Netdev: SetNetDev,
|
||||
Netmask: SetNetmask,
|
||||
Netname: SetNetName,
|
||||
Type: SetType,
|
||||
NodeNames: args,
|
||||
}
|
||||
|
||||
node_args := hostlist.Expand(args)
|
||||
|
||||
for _, a := range node_args {
|
||||
n, err := nodeDB.AddNode(a)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to add node")
|
||||
}
|
||||
wwlog.Printf(wwlog.INFO, "Added node: %s\n", a)
|
||||
|
||||
if SetClusterName != "" {
|
||||
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting cluster name to: %s\n", n.Id.Get(), SetClusterName)
|
||||
n.ClusterName.Set(SetClusterName)
|
||||
err := nodeDB.NodeUpdate(n)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to update node")
|
||||
}
|
||||
}
|
||||
|
||||
if SetNetDev != "" {
|
||||
if SetNetName == "" {
|
||||
return errors.New("you must include the '--netname' option")
|
||||
}
|
||||
|
||||
if _, ok := n.NetDevs[SetNetName]; !ok {
|
||||
var netdev node.NetDevEntry
|
||||
n.NetDevs[SetNetName] = &netdev
|
||||
}
|
||||
|
||||
wwlog.Printf(wwlog.VERBOSE, "Node: %s:%s, Setting Device to: %s\n", n.Id.Get(), SetNetName, SetNetDev)
|
||||
|
||||
n.NetDevs[SetNetName].Device.Set(SetNetDev)
|
||||
n.NetDevs[SetNetName].OnBoot.SetB(true)
|
||||
|
||||
}
|
||||
|
||||
if SetIpaddr != "" {
|
||||
if SetNetName == "" {
|
||||
return errors.New("you must include the '--netname' option")
|
||||
}
|
||||
|
||||
NewIpaddr := util.IncrementIPv4(SetIpaddr, count)
|
||||
|
||||
if _, ok := n.NetDevs[SetNetName]; !ok {
|
||||
var netdev node.NetDevEntry
|
||||
n.NetDevs[SetNetName] = &netdev
|
||||
}
|
||||
|
||||
wwlog.Printf(wwlog.VERBOSE, "Node: %s:%s, Setting Ipaddr to: %s\n", n.Id.Get(), SetNetName, NewIpaddr)
|
||||
|
||||
n.NetDevs[SetNetName].Ipaddr.Set(NewIpaddr)
|
||||
n.NetDevs[SetNetName].OnBoot.SetB(true)
|
||||
|
||||
}
|
||||
|
||||
if SetNetmask != "" {
|
||||
if SetNetName == "" {
|
||||
return errors.New("you must include the '--netname' option")
|
||||
}
|
||||
|
||||
if _, ok := n.NetDevs[SetNetName]; !ok {
|
||||
return errors.New("network device does not exist: " + SetNetName)
|
||||
}
|
||||
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 == "" {
|
||||
return errors.New("you must include the '--netname' option")
|
||||
}
|
||||
|
||||
if _, ok := n.NetDevs[SetNetName]; !ok {
|
||||
return errors.New("network device does not exist: " + SetNetName)
|
||||
}
|
||||
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 == "" {
|
||||
return errors.New("you must include the '--netname' option")
|
||||
}
|
||||
|
||||
if _, ok := n.NetDevs[SetNetName]; !ok {
|
||||
return errors.New("network device does not exist: " + SetNetName)
|
||||
}
|
||||
wwlog.Printf(wwlog.VERBOSE, "Node: %s:%s, Setting HW address to: %s\n", n.Id.Get(), SetNetName, SetHwaddr)
|
||||
|
||||
n.NetDevs[SetNetName].Hwaddr.Set(SetHwaddr)
|
||||
n.NetDevs[SetNetName].OnBoot.SetB(true)
|
||||
}
|
||||
|
||||
if SetType != "" {
|
||||
if SetNetName == "" {
|
||||
return errors.New("you must include the '--netname' option")
|
||||
}
|
||||
|
||||
if _, ok := n.NetDevs[SetNetName]; !ok {
|
||||
return errors.New("network device does not exist: " + SetNetName)
|
||||
}
|
||||
wwlog.Printf(wwlog.VERBOSE, "Node: %s:%s, Setting Type to: %s\n", n.Id.Get(), SetNetName, SetType)
|
||||
|
||||
n.NetDevs[SetNetName].Type.Set(SetType)
|
||||
}
|
||||
if SetIpaddr6 != "" {
|
||||
if SetNetName == "" {
|
||||
return errors.New("you must include the '--netname' option")
|
||||
}
|
||||
if _, ok := n.NetDevs[SetNetName]; !ok {
|
||||
return errors.New("network device does not exist: " + SetNetName)
|
||||
}
|
||||
// just check if address is a valid ipv6 CIDR address
|
||||
if _, _, err := net.ParseCIDR(SetIpaddr6); err != nil {
|
||||
return errors.Errorf("%s is not a valid ipv6 address in CIDR notation\n", SetIpaddr6)
|
||||
}
|
||||
|
||||
}
|
||||
if SetDiscoverable {
|
||||
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting node to discoverable\n", n.Id.Get())
|
||||
|
||||
n.Discoverable.SetB(true)
|
||||
}
|
||||
|
||||
err = nodeDB.NodeUpdate(n)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to update nodedb")
|
||||
}
|
||||
|
||||
count++
|
||||
}
|
||||
|
||||
err = nodeDB.Persist()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to persist new node")
|
||||
}
|
||||
|
||||
err = warewulfd.DaemonReload()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to reload warewulf daemon")
|
||||
}
|
||||
|
||||
return nil
|
||||
return node.NodeAdd(&nap)
|
||||
}
|
||||
|
||||
@@ -2,92 +2,33 @@ package delete
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
apiNode "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/hpcng/warewulf/internal/pkg/node"
|
||||
"github.com/hpcng/warewulf/internal/pkg/warewulfd"
|
||||
"github.com/hpcng/warewulf/internal/pkg/wwlog"
|
||||
"github.com/hpcng/warewulf/pkg/hostlist"
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
var count int
|
||||
var nodeList []node.NodeInfo
|
||||
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
|
||||
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "Failed to open node database: %s\n", err)
|
||||
os.Exit(1)
|
||||
ndp := wwapiv1.NodeDeleteParameter{
|
||||
Force: SetForce,
|
||||
NodeNames: args,
|
||||
}
|
||||
|
||||
nodes, err := nodeDB.FindAllNodes()
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "Could not get node list: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
args = hostlist.Expand(args)
|
||||
|
||||
for _, r := range args {
|
||||
var match bool
|
||||
for _, n := range nodes {
|
||||
if n.Id.Get() == r {
|
||||
nodeList = append(nodeList, n)
|
||||
match = true
|
||||
}
|
||||
}
|
||||
|
||||
if !match {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: No match for node: %s\n", r)
|
||||
}
|
||||
}
|
||||
|
||||
if len(nodeList) == 0 {
|
||||
fmt.Printf("No nodes found\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for _, n := range nodeList {
|
||||
err := nodeDB.DelNode(n.Id.Get())
|
||||
if !SetYes {
|
||||
var nodeList []node.NodeInfo
|
||||
// The checks run twice in the prompt case.
|
||||
// Avoiding putting in a blocking prompt in an API.
|
||||
nodeList, err = apiNode.NodeDeleteParameterCheck(&ndp, false)
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "%s\n", err)
|
||||
} else {
|
||||
count++
|
||||
fmt.Printf("Deleting node: %s\n", n.Id.Print())
|
||||
return
|
||||
}
|
||||
yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to delete %d nodes(s)", len(nodeList)))
|
||||
if !yes {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if SetYes {
|
||||
err := nodeDB.Persist()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to persist nodedb")
|
||||
}
|
||||
} else {
|
||||
q := fmt.Sprintf("Are you sure you want to delete %d nodes(s)", count)
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return apiNode.NodeDelete(&ndp)
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
var (
|
||||
baseCmd = &cobra.Command{
|
||||
DisableFlagsInUseLine: true,
|
||||
Use: "delete [OPTIONS] NODE [NODE ...]",
|
||||
Short: "Delete a node from Warewulf",
|
||||
Long: "This command will remove NODE(s) from the Warewulf node configuration.",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: CobraRunE,
|
||||
Aliases: []string{"rm", "del"},
|
||||
Use: "delete [OPTIONS] NODE [NODE ...]",
|
||||
Short: "Delete a node from Warewulf",
|
||||
Long: "This command will remove NODE(s) from the Warewulf node configuration.",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: CobraRunE,
|
||||
Aliases: []string{"rm", "del"},
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
@@ -29,11 +29,11 @@ var (
|
||||
},
|
||||
}
|
||||
SetYes bool
|
||||
SetForce string
|
||||
SetForce bool // currently unused
|
||||
)
|
||||
|
||||
func init() {
|
||||
baseCmd.PersistentFlags().StringVarP(&SetForce, "force", "f", "", "Force node delete")
|
||||
baseCmd.PersistentFlags().BoolVarP(&SetForce, "force", "f", false, "Force node delete")
|
||||
baseCmd.PersistentFlags().BoolVarP(&SetYes, "yes", "y", false, "Set 'yes' to all questions asked")
|
||||
|
||||
}
|
||||
@@ -41,4 +41,4 @@ func init() {
|
||||
// GetRootCommand returns the root cobra.Command for the application.
|
||||
func GetCommand() *cobra.Command {
|
||||
return baseCmd
|
||||
}
|
||||
}
|
||||
@@ -2,131 +2,127 @@ package list
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hpcng/warewulf/internal/pkg/node"
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/node"
|
||||
"github.com/hpcng/warewulf/internal/pkg/wwlog"
|
||||
"github.com/hpcng/warewulf/pkg/hostlist"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
|
||||
|
||||
nodeDB, err := node.New()
|
||||
nodeInfo, err := node.NodeList(args)
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "Could not open node configuration: %s\n", err)
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
|
||||
nodes, err := nodeDB.FindAllNodes()
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "Could not get node list: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
args = hostlist.Expand(args)
|
||||
|
||||
if ShowAll {
|
||||
for _, node := range node.FilterByName(nodes, args) {
|
||||
for i := 0; i < len(nodeInfo); i++ {
|
||||
ni := nodeInfo[i]
|
||||
nodeName := ni.Id.Value
|
||||
|
||||
fmt.Printf("################################################################################\n")
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", "NODE", "FIELD", "PROFILE", "VALUE")
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "Id", node.Id.Source(), node.Id.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "Comment", node.Comment.Source(), node.Comment.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "Cluster", node.ClusterName.Source(), node.ClusterName.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "Profiles", "--", strings.Join(node.Profiles, ","))
|
||||
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "Discoverable", node.Discoverable.Source(), node.Discoverable.PrintB())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, "Id", ni.Id.Source, ni.Id.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, "Comment", ni.Comment.Source, ni.Comment.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, "Cluster", ni.Cluster.Source, ni.Cluster.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, "Profiles", "--", strings.Join(ni.Profiles, ",'"))
|
||||
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "Container", node.ContainerName.Source(), node.ContainerName.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "KernelOverride", node.Kernel.Override.Source(), node.Kernel.Override.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "KernelArgs", node.Kernel.Args.Source(), node.Kernel.Args.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "SystemOverlay", node.SystemOverlay.Source(), node.SystemOverlay.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "RuntimeOverlay", node.RuntimeOverlay.Source(), node.RuntimeOverlay.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "Ipxe", node.Ipxe.Source(), node.Ipxe.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "Init", node.Init.Source(), node.Init.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "Root", node.Root.Source(), node.Root.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "AssetKey", node.AssetKey.Source(), node.AssetKey.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, "Discoverable", ni.Discoverable.Source, ni.Discoverable.Print)
|
||||
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "IpmiIpaddr", node.Ipmi.Ipaddr.Source(), node.Ipmi.Ipaddr.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "IpmiNetmask", node.Ipmi.Netmask.Source(), node.Ipmi.Netmask.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "IpmiPort", node.Ipmi.Port.Source(), node.Ipmi.Port.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "IpmiGateway", node.Ipmi.Gateway.Source(), node.Ipmi.Gateway.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "IpmiUserName", node.Ipmi.UserName.Source(), node.Ipmi.UserName.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "IpmiInterface", node.Ipmi.Interface.Source(), node.Ipmi.Interface.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "IpmiWrite", node.Ipmi.Interface.Source(), node.Ipmi.Write.PrintB())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, "Container", ni.Container.Source, ni.Container.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, "KernelOverride", ni.KernelOverride.Source, ni.KernelOverride.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, "KernelArgs", ni.KernelArgs.Source, ni.KernelArgs.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, "SystemOverlay", ni.SystemOverlay.Source, ni.SystemOverlay.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, "RuntimeOverlay", ni.RuntimeOverlay.Source, ni.RuntimeOverlay.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, "Ipxe", ni.Ipxe.Source, ni.Ipxe.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, "Init", ni.Init.Source, ni.Init.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, "Root", ni.Root.Source, ni.Root.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, "AssetKey", ni.AssetKey.Source, ni.AssetKey.Print)
|
||||
|
||||
for keyname, key := range node.Tags {
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), "Tag["+keyname+"]", key.Source(), key.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, "IpmiIpaddr", ni.IpmiIpaddr.Source, ni.IpmiIpaddr.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, "IpmiNetmask", ni.IpmiNetmask.Source, ni.IpmiNetmask.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, "IpmiPort", ni.IpmiPort.Source, ni.IpmiPort.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, "IpmiGateway", ni.IpmiGateway.Source, ni.IpmiGateway.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, "IpmiUserName", ni.IpmiUserName.Source, ni.IpmiUserName.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, "IpmiInterface", ni.IpmiInterface.Source, ni.IpmiInterface.Print)
|
||||
|
||||
for keyname, key := range ni.Tags {
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, "Tag["+keyname+"]", key.Source, key.Print)
|
||||
}
|
||||
|
||||
for name, netdev := range node.NetDevs {
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), name+":DEVICE", netdev.Device.Source(), netdev.Device.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), name+":HWADDR", netdev.Hwaddr.Source(), netdev.Hwaddr.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), name+":IPADDR", netdev.Ipaddr.Source(), netdev.Ipaddr.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), name+":IPADDR6", netdev.Ipaddr.Source(), netdev.Ipaddr6.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), name+":NETMASK", netdev.Netmask.Source(), netdev.Netmask.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), name+":GATEWAY", netdev.Gateway.Source(), netdev.Gateway.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), name+":TYPE", netdev.Type.Source(), netdev.Type.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), name+":ONBOOT", netdev.OnBoot.Source(), netdev.OnBoot.PrintB())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), name+":PRIMARY", netdev.Primary.Source(), netdev.Primary.PrintB())
|
||||
for name, netdev := range ni.NetDevs {
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, name+":DEVICE", netdev.Device.Source, netdev.Device.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, name+":HWADDR", netdev.Hwaddr.Source, netdev.Hwaddr.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, name+":IPADDR", netdev.Ipaddr.Source, netdev.Ipaddr.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, name+":NETMASK", netdev.Netmask.Source, netdev.Netmask.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, name+":GATEWAY", netdev.Gateway.Source, netdev.Gateway.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, name+":TYPE", netdev.Type.Source, netdev.Type.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, name+":ONBOOT", netdev.Onboot.Source, netdev.Onboot.Print)
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, name+":DEFAULT", netdev.Primary.Source, netdev.Primary.Print)
|
||||
for keyname, key := range netdev.Tags {
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", node.Id.Get(), name+":TAG["+keyname+"]", key.Source(), key.Print())
|
||||
fmt.Printf("%-20s %-18s %-12s %s\n", nodeName, name+":TAG["+keyname+"]", key.Source, key.Print)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else if ShowNet {
|
||||
fmt.Printf("%-22s %-6s %-18s %-15s %-15s %-15s\n", "NODE NAME", "DEVICE", "HWADDR", "IPADDR", "GATEWAY", "IPADDR6")
|
||||
fmt.Printf("%-22s %-6s %-18s %-15s %-15s\n", "NODE NAME", "DEVICE", "HWADDR", "IPADDR", "GATEWAY")
|
||||
fmt.Println(strings.Repeat("=", 80))
|
||||
|
||||
for _, node := range node.FilterByName(nodes, args) {
|
||||
if len(node.NetDevs) > 0 {
|
||||
for name, dev := range node.NetDevs {
|
||||
fmt.Printf("%-22s %-6s %-18s %-15s %-15s %-15s\n",
|
||||
node.Id.Get(), name, dev.Hwaddr.Print(), dev.Ipaddr.Print(), dev.Gateway.Print(), dev.Ipaddr6.Print())
|
||||
for i := 0; i < len(nodeInfo); i++ {
|
||||
ni := nodeInfo[i]
|
||||
nodeName := ni.Id.Value
|
||||
|
||||
if len(ni.NetDevs) > 0 {
|
||||
for name, dev := range ni.NetDevs {
|
||||
fmt.Printf("%-22s %-6s %-18s %-15s %-15s\n", nodeName, name, dev.Hwaddr.Print, dev.Ipaddr.Print, dev.Gateway.Print)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("%-22s %-6s %-18s %-15s %-15s %-15s\n", node.Id.Get(), "--", "--", "--", "--", "--")
|
||||
fmt.Printf("%-22s %-6s %-18s %-15s %-15s\n", nodeName, "--", "--", "--", "--")
|
||||
}
|
||||
}
|
||||
|
||||
} else if ShowIpmi {
|
||||
fmt.Printf("%-22s %-16s %-10s %-20s %-20s %-14s\n", "NODE NAME", "IPMI IPADDR", "IPMI PORT", "IPMI USERNAME", "IPMI PASSWORD", "IPMI INTERFACE")
|
||||
fmt.Println(strings.Repeat("=", 108))
|
||||
|
||||
for _, node := range node.FilterByName(nodes, args) {
|
||||
fmt.Printf("%-22s %-16s %-10s %-20s %-20s %-14s\n", node.Id.Get(), node.Ipmi.Ipaddr.Print(), node.Ipmi.Port.Print(), node.Ipmi.UserName.Print(), node.Ipmi.Password.Print(), node.Ipmi.Interface.Print())
|
||||
for i := 0; i < len(nodeInfo); i++ {
|
||||
ni := nodeInfo[i]
|
||||
nodeName := ni.Id.Value
|
||||
|
||||
fmt.Printf("%-22s %-16s %-10s %-20s %-20s %-14s\n", nodeName, ni.IpmiIpaddr.Print, ni.IpmiPort.Print, ni.IpmiUserName.Print, ni.IpmiPassword.Print, ni.IpmiInterface.Print)
|
||||
}
|
||||
|
||||
} else if ShowLong {
|
||||
fmt.Printf("%-22s %-26s %-35s %s\n", "NODE NAME", "KERNEL OVERRIDE", "CONTAINER", "OVERLAYS (S/R)")
|
||||
fmt.Println(strings.Repeat("=", 120))
|
||||
|
||||
for _, node := range node.FilterByName(nodes, args) {
|
||||
fmt.Printf("%-22s %-26s %-35s %s\n", node.Id.Get(), node.Kernel.Override.Print(), node.ContainerName.Print(), node.SystemOverlay.Print()+"/"+node.RuntimeOverlay.Print())
|
||||
for i := 0; i < len(nodeInfo); i++ {
|
||||
ni := nodeInfo[i]
|
||||
nodeName := ni.Id.Value
|
||||
|
||||
fmt.Printf("%-22s %-26s %-35s %s\n", nodeName, ni.KernelOverride.Print, ni.Container.Print, ni.SystemOverlay.Print+"/"+ni.RuntimeOverlay.Print)
|
||||
}
|
||||
|
||||
} else {
|
||||
fmt.Printf("%-22s %-26s %s\n", "NODE NAME", "PROFILES", "NETWORK")
|
||||
fmt.Println(strings.Repeat("=", 80))
|
||||
|
||||
for _, node := range node.FilterByName(nodes, args) {
|
||||
for i := 0; i < len(nodeInfo); i++ {
|
||||
ni := nodeInfo[i]
|
||||
nodeName := ni.Id.Value
|
||||
|
||||
var netdevs []string
|
||||
if len(node.NetDevs) > 0 {
|
||||
for name, dev := range node.NetDevs {
|
||||
netdevs = append(netdevs, fmt.Sprintf("%s:%s", name, dev.Ipaddr.Print()))
|
||||
if len(ni.NetDevs) > 0 {
|
||||
for name, dev := range ni.NetDevs {
|
||||
netdevs = append(netdevs, fmt.Sprintf("%s:%s", name, dev.Ipaddr.Print))
|
||||
}
|
||||
}
|
||||
sort.Strings(netdevs)
|
||||
|
||||
fmt.Printf("%-22s %-26s %s\n", node.Id.Get(), strings.Join(node.Profiles, ","), strings.Join(netdevs, ", "))
|
||||
fmt.Printf("%-22s %-26s %s\n", nodeName, strings.Join(ni.Profiles, ","), strings.Join(netdevs, ", "))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package nodestatus
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/node"
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
|
||||
"github.com/hpcng/warewulf/internal/pkg/warewulfconf"
|
||||
"github.com/hpcng/warewulf/internal/pkg/wwlog"
|
||||
"github.com/hpcng/warewulf/pkg/hostlist"
|
||||
@@ -17,19 +17,7 @@ import (
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
type allStatus struct {
|
||||
Nodes map[string]*NodeStatus `json:"nodes"`
|
||||
}
|
||||
|
||||
type NodeStatus struct {
|
||||
NodeName string `json:"node name"`
|
||||
Stage string `json:"stage"`
|
||||
Sent string `json:"sent"`
|
||||
Ipaddr string `json:"ipaddr"`
|
||||
Lastseen int64 `json:"last seen"`
|
||||
}
|
||||
|
||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
|
||||
|
||||
controller, err := warewulfconf.New()
|
||||
if err != nil {
|
||||
@@ -42,30 +30,16 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
statusURL := fmt.Sprintf("http://%s:%d/status", controller.Ipaddr, controller.Warewulf.Port)
|
||||
|
||||
for {
|
||||
var elipsis bool
|
||||
var height int
|
||||
var count int
|
||||
rightnow := time.Now().Unix()
|
||||
|
||||
wwlog.Printf(wwlog.VERBOSE, "Connecting to: %s\n", statusURL)
|
||||
|
||||
resp, err := http.Get(statusURL)
|
||||
var nodeStatusResponse *wwapiv1.NodeStatusResponse
|
||||
nodeStatusResponse, err = node.NodeStatus([]string{})
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "Could not connect to Warewulf server: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
var nodeStatus allStatus
|
||||
|
||||
err = decoder.Decode(&nodeStatus)
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "Could not decode JSON: %s\n", err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
||||
|
||||
if SetWatch {
|
||||
@@ -80,65 +54,50 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
fmt.Printf("%-20s %-20s %-25s %-10s\n", "NODENAME", "STAGE", "SENT", "LASTSEEN (s)")
|
||||
fmt.Printf("%s\n", strings.Repeat("=", 80))
|
||||
|
||||
keys := make([]*NodeStatus, 0, len(nodeStatus.Nodes))
|
||||
|
||||
wwlog.Printf(wwlog.VERBOSE, "Building sort index\n")
|
||||
var statuses []*wwapiv1.NodeStatus
|
||||
if len(args) > 0 {
|
||||
tmpMap := make(map[string]bool)
|
||||
nodeList := hostlist.Expand(args)
|
||||
|
||||
for _, name := range nodeList {
|
||||
tmpMap[name] = true
|
||||
}
|
||||
|
||||
for name := range nodeStatus.Nodes {
|
||||
if _, ok := tmpMap[name]; ok {
|
||||
keys = append(keys, nodeStatus.Nodes[name])
|
||||
for i := 0; i < len(nodeStatusResponse.NodeStatus); i++ {
|
||||
for j := 0; j < len(nodeList); j++ {
|
||||
if nodeStatusResponse.NodeStatus[i].NodeName == nodeList[j] {
|
||||
statuses = append(statuses, nodeStatusResponse.NodeStatus[i])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
for name := range nodeStatus.Nodes {
|
||||
keys = append(keys, nodeStatus.Nodes[name])
|
||||
for i := 0; i < len(nodeStatusResponse.NodeStatus); i++ {
|
||||
statuses = append(statuses, nodeStatusResponse.NodeStatus[i])
|
||||
}
|
||||
}
|
||||
|
||||
wwlog.Printf(wwlog.VERBOSE, "Sorting index\n")
|
||||
if SetSortLast {
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
if keys[i].Lastseen > keys[j].Lastseen {
|
||||
sort.Slice(statuses, func(i, j int) bool {
|
||||
if statuses[i].Lastseen > statuses[j].Lastseen {
|
||||
return true
|
||||
} else if keys[i].Lastseen < keys[j].Lastseen {
|
||||
} else if statuses[i].Lastseen < statuses[j].Lastseen {
|
||||
return false
|
||||
} else {
|
||||
if keys[i].NodeName < keys[j].NodeName {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return statuses[i].NodeName < statuses[j].NodeName
|
||||
}
|
||||
//return keys[i].Lastseen > keys[j].Lastseen
|
||||
})
|
||||
} else {
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
return keys[i].NodeName < keys[j].NodeName
|
||||
})
|
||||
}
|
||||
|
||||
if SetSortReverse {
|
||||
var tmpsort []*NodeStatus
|
||||
|
||||
} else if SetSortReverse {
|
||||
wwlog.Printf(wwlog.VERBOSE, "Reversing sort order\n")
|
||||
sort.Slice(statuses, func(i, j int) bool {
|
||||
return statuses[i].NodeName > statuses[j].NodeName
|
||||
})
|
||||
|
||||
for l := len(keys) - 1; l >= 0; l-- {
|
||||
tmpsort = append(tmpsort, keys[l])
|
||||
}
|
||||
|
||||
keys = tmpsort
|
||||
} else {
|
||||
sort.Slice(statuses, func(i, j int) bool {
|
||||
return statuses[i].NodeName < statuses[j].NodeName
|
||||
})
|
||||
}
|
||||
|
||||
wwlog.Printf(wwlog.VERBOSE, "Printing results\n")
|
||||
for _, o := range keys {
|
||||
for i := 0; i < len(statuses); i++ {
|
||||
o := statuses[i]
|
||||
if SetTime > 0 && o.Lastseen < SetTime {
|
||||
continue
|
||||
}
|
||||
@@ -158,7 +117,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
color.HiBlack("%-20s %-20s %-25s %-10s\n", o.NodeName, "--", "--", "--")
|
||||
}
|
||||
if count+4 >= height && SetWatch {
|
||||
if count+1 != len(keys) {
|
||||
if count+1 != len(statuses) {
|
||||
elipsis = true
|
||||
}
|
||||
break
|
||||
@@ -175,6 +134,5 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,29 +3,16 @@ package version
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hpcng/warewulf/internal/pkg/buildconfig"
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
|
||||
"github.com/hpcng/warewulf/internal/pkg/version"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
if ListFull {
|
||||
fmt.Printf("%s=%s\n", "VERSION", version.GetVersion())
|
||||
fmt.Printf("%s=%s\n", "BINDIR", buildconfig.BINDIR())
|
||||
fmt.Printf("%s=%s\n", "DATADIR", buildconfig.DATADIR())
|
||||
fmt.Printf("%s=%s\n", "SYSCONFDIR", buildconfig.SYSCONFDIR())
|
||||
fmt.Printf("%s=%s\n", "LOCALSTATEDIR", buildconfig.LOCALSTATEDIR())
|
||||
fmt.Printf("%s=%s\n", "SRVDIR", buildconfig.SRVDIR())
|
||||
fmt.Printf("%s=%s\n", "TFTPDIR", buildconfig.TFTPDIR())
|
||||
fmt.Printf("%s=%s\n", "SYSTEMDDIR", buildconfig.SYSTEMDDIR())
|
||||
fmt.Printf("%s=%s\n", "WWOVERLAYDIR", buildconfig.WWOVERLAYDIR())
|
||||
fmt.Printf("%s=%s\n", "WWCHROOTDIR", buildconfig.WWCHROOTDIR())
|
||||
fmt.Printf("%s=%s\n", "WWPROVISIONDIR", buildconfig.WWPROVISIONDIR())
|
||||
fmt.Printf("%s=%s\n", "BASEVERSION", buildconfig.VERSION())
|
||||
fmt.Printf("%s=%s\n", "RELEASE", buildconfig.RELEASE())
|
||||
fmt.Printf("%s=%s\n", "WWCLIENTDIR", buildconfig.WWCLIENTDIR())
|
||||
} else {
|
||||
fmt.Println(version.GetVersion())
|
||||
}
|
||||
|
||||
fmt.Println("wwctl version:\t", version.GetVersion())
|
||||
|
||||
var wwVersionResponse *wwapiv1.VersionResponse = version.Version()
|
||||
fmt.Println("rpc version:", wwVersionResponse.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user