Files
warewulf/internal/pkg/api/image/image.go
Matt Hink be73ac056a Initial cut for removing the old API.
Remove API reverse proxy server and google dependencies.
Remove API config.
Remove API client.
Remove API GRPC server.
Remove unused ImageCopy function. image.Duplicate is used.
Update Makefile to avoid protobuf builds.

Signed-off-by: Matt Hink <mhink@ciq.com>
2025-05-10 10:58:03 -06:00

400 lines
9.5 KiB
Go

package image
import (
"fmt"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"github.com/containers/image/v5/types"
"github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1"
"github.com/warewulf/warewulf/internal/pkg/image"
"github.com/warewulf/warewulf/internal/pkg/kernel"
"github.com/warewulf/warewulf/internal/pkg/node"
"github.com/warewulf/warewulf/internal/pkg/util"
"github.com/warewulf/warewulf/internal/pkg/warewulfd"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
func ImageBuild(cbp *wwapiv1.ImageBuildParameter) (err error) {
if cbp == nil {
return fmt.Errorf("input parameter is nil")
}
var images []string
if cbp.All {
images, err = image.ListSources()
} else {
images = cbp.ImageNames
}
if len(images) == 0 {
return
}
for _, c := range images {
if !image.ValidSource(c) {
return fmt.Errorf("image name does not exist: %s", c)
}
err = image.Build(c, cbp.Force)
if err != nil {
return fmt.Errorf("could not build image %s: %s", c, err)
}
}
return
}
func ImageDelete(cdp *wwapiv1.ImageDeleteParameter) (err error) {
if cdp == nil {
return fmt.Errorf("input parameter is nil")
}
nodeDB, err := node.New()
if err != nil {
return fmt.Errorf("could not open nodeDB: %s", err)
}
ARG_LOOP:
for i := 0; i < len(cdp.ImageNames); i++ {
//_, arg := range args {
imageName := cdp.ImageNames[i]
for _, n := range nodeDB.Nodes {
if n.ImageName == imageName {
wwlog.Error("image %s is in use by node %s, skipping", imageName, n.Id())
continue ARG_LOOP
}
}
for _, p := range nodeDB.NodeProfiles {
if p.ImageName == imageName {
wwlog.Error("image %s is in use by profile %s, skipping", imageName, p.Id())
continue ARG_LOOP
}
}
if !image.ValidSource(imageName) {
wwlog.Error("image name is not a valid source: %s", imageName)
continue
}
err := image.DeleteSource(imageName)
if err != nil {
wwlog.Error("could not remove source: %s", imageName)
}
err = image.DeleteImage(imageName)
if err != nil {
wwlog.Error("could not remove image files %s", imageName)
}
fmt.Printf("Image has been deleted: %s\n", imageName)
}
return
}
func ImageImport(cip *wwapiv1.ImageImportParameter) (imageName string, err error) {
if cip == nil {
err = fmt.Errorf("input parameter is nil")
return
}
if cip.Name == "" {
name := path.Base(cip.Source)
wwlog.Info("Setting image name: %s", name)
cip.Name = name
}
if !image.ValidName(cip.Name) {
err = fmt.Errorf("image name contains illegal characters: %s", cip.Name)
return
}
imageName = cip.Name
fullPath := image.SourceDir(cip.Name)
// image already exists and should be removed first
if util.IsDir(fullPath) && cip.Force {
wwlog.Info("Overwriting existing image")
err = os.RemoveAll(fullPath)
if err != nil {
return
}
}
if util.IsDir(fullPath) {
if !cip.Update {
err = fmt.Errorf("image name exists, specify --force, --update, or choose a different name: %s", cip.Name)
return
}
wwlog.Info("Updating existing image")
} else if strings.HasPrefix(cip.Source, "docker://") || strings.HasPrefix(cip.Source, "docker-daemon://") ||
strings.HasPrefix(cip.Source, "file://") || util.IsFile(cip.Source) {
var sCtx *types.SystemContext
sCtx, err = GetSystemContext(cip.OciNoHttps, cip.OciUsername, cip.OciPassword, cip.Platform)
if err != nil {
return
}
if util.IsFile(cip.Source) && !filepath.IsAbs(cip.Source) {
cip.Source, err = filepath.Abs(cip.Source)
if err != nil {
err = fmt.Errorf("when resolving absolute path of %s, err: %v", cip.Source, err)
return
}
}
err = image.ImportDocker(cip.Source, cip.Name, sCtx)
if err != nil {
err = fmt.Errorf("could not import image: %s", err.Error())
_ = image.DeleteSource(cip.Name)
return
}
} else if util.IsDir(cip.Source) {
err = image.ImportDirectory(cip.Source, cip.Name)
if err != nil {
err = fmt.Errorf("could not import image: %s", err.Error())
_ = image.DeleteSource(cip.Name)
return
}
} else {
err = fmt.Errorf("invalid dir or uri: %s", cip.Source)
return
}
if cip.SyncUser {
err = image.Syncuser(cip.Name, true)
if err != nil {
err = fmt.Errorf("syncuser error: %w", err)
return
}
}
if cip.Build {
wwlog.Info("Building image: %s", cip.Name)
err = image.Build(cip.Name, true)
if err != nil {
err = fmt.Errorf("could not build image %s: %s", cip.Name, err.Error())
return
}
}
return
}
func ImageList() (imageInfo []*wwapiv1.ImageInfo, err error) {
var sources []string
sources, err = image.ListSources()
if err != nil {
wwlog.Error("%s", err)
return
}
nodeDB, err := node.New()
if err != nil {
wwlog.Error("%s", err)
return
}
nodes, err := nodeDB.FindAllNodes()
if err != nil {
wwlog.Error("%s", err)
return
}
nodemap := make(map[string]int)
for _, n := range nodes {
nodemap[n.ImageName]++
}
for _, source := range sources {
if nodemap[source] == 0 {
nodemap[source] = 0
}
wwlog.Debug("Finding kernel version for: %s", source)
kernel := kernel.FindKernels(source).Default()
kernelVersion := ""
if kernel != nil {
kernelVersion = kernel.Version()
}
var creationTime uint64
sourceStat, err := os.Stat(image.SourceDir(source))
wwlog.Debug("Checking creation time for: %s,%v", image.SourceDir(source), sourceStat.ModTime())
if err != nil {
wwlog.Error("%s", err)
} else {
creationTime = uint64(sourceStat.ModTime().Unix())
}
var modTime uint64
imageStat, err := os.Stat(image.ImageFile(source))
if err == nil {
modTime = uint64(imageStat.ModTime().Unix())
}
imgSize := 0
if imgF, err := os.Stat(image.ImageFile(source)); err == nil {
imgSize = int(imgF.Size())
}
imgCSize := 0
if imgFC, err := os.Stat(image.ImageFile(source) + ".gz"); err == nil {
imgCSize = int(imgFC.Size())
}
imageInfo = append(imageInfo, &wwapiv1.ImageInfo{
Name: source,
NodeCount: uint32(nodemap[source]),
KernelVersion: kernelVersion,
CreateDate: creationTime,
ModDate: modTime,
ImgSize: uint64(imgSize),
ImgSizeComp: uint64(imgCSize),
})
}
return
}
func ImageShow(csp *wwapiv1.ImageShowParameter) (response *wwapiv1.ImageShowResponse, err error) {
imageName := csp.ImageName
if !image.ValidName(imageName) {
err = fmt.Errorf("%s is not a valid image name", imageName)
return
}
rootFsDir := image.RootFsDir(imageName)
if !util.IsDir(rootFsDir) {
err = fmt.Errorf("%s is not a valid image", imageName)
return
}
kernel := kernel.FindKernels(imageName).Default()
kernelVersion := ""
if kernel != nil {
kernelVersion = kernel.Version()
}
nodeDB, err := node.New()
if err != nil {
return
}
nodes, err := nodeDB.FindAllNodes()
if err != nil {
return
}
var nodeList []string
for _, n := range nodes {
if n.ImageName == imageName {
nodeList = append(nodeList, n.Id())
}
}
response = &wwapiv1.ImageShowResponse{
Name: imageName,
Rootfs: rootFsDir,
Nodes: nodeList,
KernelVersion: kernelVersion,
}
return
}
func ImageRename(crp *wwapiv1.ImageRenameParameter) (err error) {
// rename the image source folder
sourceDir := image.SourceDir(crp.ImageName)
destDir := image.SourceDir(crp.TargetName)
err = os.Rename(sourceDir, destDir)
if err != nil {
return err
}
err = image.DeleteImage(crp.ImageName)
if err != nil {
wwlog.Warn("Could not remove image files for %s: %s", crp.ImageName, err)
}
if crp.Build {
err = image.Build(crp.TargetName, true)
if err != nil {
return err
}
}
// update the nodes profiles image name
nodeDB, err := node.New()
if err != nil {
return err
}
for nodeId, node := range nodeDB.Nodes {
if node.ImageName == crp.ImageName {
wwlog.Debug("updating node %s image to %s", nodeId, crp.TargetName)
nodeDB.Nodes[nodeId].ImageName = crp.TargetName
}
}
for profileId, profile := range nodeDB.NodeProfiles {
if profile.ImageName == crp.ImageName {
wwlog.Debug("updating profile %s image to %s", profileId, crp.TargetName)
nodeDB.NodeProfiles[profileId].ImageName = crp.TargetName
}
}
err = nodeDB.Persist()
if err != nil {
return err
}
err = warewulfd.DaemonStatus()
if err != nil {
// warewulfd is not running, skip
return nil
}
// else reload daemon to apply new changes
return warewulfd.DaemonReload()
}
// create the system context and reading out environment variables
func GetSystemContext(noHttps bool, username string, password string, platform string) (sCtx *types.SystemContext, err error) {
sCtx = &types.SystemContext{}
// only check env if noHttps wasn't set
if !noHttps {
val, ok := os.LookupEnv("WAREWULF_OCI_NOHTTPS")
if ok {
noHttps, err = strconv.ParseBool(val)
if err != nil {
return nil, 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
}
if username == "" {
username, _ = os.LookupEnv("WAREWULF_OCI_USERNAME")
}
if password == "" {
password, _ = os.LookupEnv("WAREWULF_OCI_PASSWORD")
}
if username != "" || password != "" {
if username != "" && password != "" {
sCtx.DockerAuthConfig = &types.DockerAuthConfig{
Username: username,
Password: password,
}
} else {
return nil, fmt.Errorf("oci username and password env vars must be specified together")
}
}
if platform == "" {
platform, _ = os.LookupEnv("WAREWULF_OCI_PLATFORM")
}
if platform != "" {
sCtx.ArchitectureChoice = platform
}
return sCtx, nil
}