Files
warewulf/internal/app/api/wwapid/wwapid.go
Jonathon Anderson 45a690ca4e Rename "container" to "image"
- Updated `wwctl upgrade` to handle updates
- Maintained `.Container` and `.ContainerName` in tstruct
- Added `ContainerName()` methods to node and profile objects
- Added `--container`, `-C` aliases to wwctl commands (`<node|profile> <add|set>`)
- Added `wwctl container` alias
- Added support for `container_exit.sh` if `image_exit.sh` is not found

Signed-off-by: Jonathon Anderson <janderson@ciq.com>
2025-01-19 05:54:19 -07:00

357 lines
9.2 KiB
Go

package main
import (
"bufio"
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"log"
"net"
"os"
"path"
"github.com/warewulf/warewulf/internal/pkg/api/apiconfig"
"github.com/warewulf/warewulf/internal/pkg/api/image"
apinode "github.com/warewulf/warewulf/internal/pkg/api/node"
"github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1"
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
"github.com/warewulf/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")
conf := warewulfconf.Get()
// Read the config file.
config, err := apiconfig.NewServer(path.Join(conf.Paths.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 = os.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.
// ImageBuild builds one or more images.
func (s *apiServer) ImageBuild(ctx context.Context, request *wwapiv1.ImageBuildParameter) (response *wwapiv1.ImageListResponse, err error) {
// Parameter checks.
if request == nil {
return response, status.Errorf(codes.InvalidArgument, "nil request")
}
if request.ImageNames == nil {
return response, status.Errorf(codes.InvalidArgument, "nil request.ImageNames")
}
// Build the image.
err = image.ImageBuild(request)
if err != nil {
return
}
// Return the built images. (A REST POST returns what is modified.)
var images []*wwapiv1.ImageInfo
images, err = image.ImageList()
if err != nil {
return
}
response = &wwapiv1.ImageListResponse{}
for i := 0; i < len(images); i++ {
for j := 0; j < len(request.ImageNames); j++ {
if images[i].Name == request.ImageNames[j] {
response.Images = append(response.Images, images[i])
}
}
}
return
}
// ImageCopy duplicates an image.
func (s *apiServer) ImageCopy(ctx context.Context, request *wwapiv1.ImageCopyParameter) (response *emptypb.Empty, err error) {
// Parameter checks.
if request == nil {
return response, status.Errorf(codes.InvalidArgument, "nil request")
}
err = image.ImageCopy(request)
return
}
// ImageDelete deletes one or more images from Warewulf.
func (s *apiServer) ImageDelete(ctx context.Context, request *wwapiv1.ImageDeleteParameter) (response *emptypb.Empty, err error) {
// Parameter checks.
if request == nil {
return response, status.Errorf(codes.InvalidArgument, "nil request")
}
if request.ImageNames == nil {
return response, status.Errorf(codes.InvalidArgument, "nil request.ImageNames")
}
err = image.ImageDelete(request)
return
}
func (s *apiServer) ImageImport(ctx context.Context, request *wwapiv1.ImageImportParameter) (response *wwapiv1.ImageListResponse, err error) {
// Import the image.
var imageName string
imageName, err = image.ImageImport(request)
if err != nil {
return
}
// Return the imported image to the client.
var images []*wwapiv1.ImageInfo
images, err = image.ImageList()
if err != nil {
return
}
// Image name may have been shimmed in during import,
// which is why ImageImport returns it.
for i := 0; i < len(images); i++ {
if imageName == images[i].Name {
response = &wwapiv1.ImageListResponse{
Images: []*wwapiv1.ImageInfo{images[i]},
}
return
}
}
return
}
// ImageList returns details about images.
func (s *apiServer) ImageList(ctx context.Context, request *emptypb.Empty) (response *wwapiv1.ImageListResponse, err error) {
var images []*wwapiv1.ImageInfo
images, err = image.ImageList()
if err != nil {
return
}
response = &wwapiv1.ImageListResponse{
Images: images,
}
return
}
// ImageShow returns details about images.
func (s *apiServer) ImageShow(ctx context.Context, request *wwapiv1.ImageShowParameter) (response *wwapiv1.ImageShowResponse, err error) {
// Parameter checks.
if request == nil {
return response, status.Errorf(codes.InvalidArgument, "nil request")
}
return image.ImageShow(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 = apinode.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 = apinode.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.ConfSetParameter) (response *wwapiv1.NodeListResponse, err error) {
// Parameter checks.
if request == nil {
return response, status.Errorf(codes.InvalidArgument, "nil request")
}
if request.ConfList == nil {
return response, status.Errorf(codes.InvalidArgument, "nil request.NodeNames")
}
// Perform the NodeSet.
err = apinode.NodeSet(request)
if err != nil {
return
}
// Return the updated nodes as per REST.
return s.nodeListInternal(request.ConfList)
}
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 apinode.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 = apinode.NodeList(nodeNames)
if err != nil {
return
}
*/
response = &wwapiv1.NodeListResponse{
Nodes: nodes,
}
return
}