Add local build API for direct filesystem builds on MacOS and Windows (only WSL)

Adds /libpod/local/build endpoint, client bindings, and path translation
utilities to enable container builds from mounted directories to podman machine without tar uploads.

This optimization significantly speeds up build operations when working with remote Podman machines by eliminating redundant file transfers for already-accessible files.

Fixes: https://issues.redhat.com/browse/RUN-3249

Signed-off-by: Jan Rodák <hony.com@seznam.cz>
This commit is contained in:
Jan Rodák
2025-09-12 14:30:01 +02:00
parent 685a448ccc
commit a1e7e9a46d
11 changed files with 812 additions and 17 deletions

View File

@@ -13,6 +13,7 @@ import (
"strings"
"github.com/containers/podman/v5/pkg/bindings"
"github.com/containers/podman/v5/pkg/domain/entities"
"github.com/containers/podman/v5/pkg/machine/define"
"github.com/containers/podman/v5/pkg/machine/env"
"github.com/containers/podman/v5/pkg/machine/provider"
@@ -154,3 +155,113 @@ func CheckPathOnRunningMachine(ctx context.Context, path string) (*LocalAPIMap,
return isPathAvailableOnMachine(mounts, vmType, path)
}
// CheckIfImageBuildPathsOnRunningMachine checks if the build context directory and all specified
// Containerfiles are available on the running machine. If they are, it translates their paths
// to the corresponding remote paths and returns them along with a flag indicating success.
func CheckIfImageBuildPathsOnRunningMachine(ctx context.Context, containerFiles []string, options entities.BuildOptions) ([]string, entities.BuildOptions, bool) {
if machineMode := bindings.GetMachineMode(ctx); !machineMode {
logrus.Debug("Machine mode is not enabled, skipping machine check")
return nil, options, false
}
conn, err := bindings.GetClient(ctx)
if err != nil {
logrus.Debugf("Failed to get client connection: %v", err)
return nil, options, false
}
mounts, vmType, err := getMachineMountsAndVMType(conn.URI.String(), conn.URI)
if err != nil {
logrus.Debugf("Failed to get machine mounts: %v", err)
return nil, options, false
}
// Context directory
if err := fileutils.Lexists(options.ContextDirectory); errors.Is(err, fs.ErrNotExist) {
logrus.Debugf("Path %s does not exist locally, skipping machine check", options.ContextDirectory)
return nil, options, false
}
mapping, found := isPathAvailableOnMachine(mounts, vmType, options.ContextDirectory)
if !found {
logrus.Debugf("Path %s is not available on the running machine", options.ContextDirectory)
return nil, options, false
}
options.ContextDirectory = mapping.RemotePath
// Containerfiles
translatedContainerFiles := []string{}
for _, containerFile := range containerFiles {
if strings.HasPrefix(containerFile, "http://") || strings.HasPrefix(containerFile, "https://") {
translatedContainerFiles = append(translatedContainerFiles, containerFile)
continue
}
// If Containerfile does not exist, assume it is in context directory
if err := fileutils.Lexists(containerFile); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
logrus.Fatalf("Failed to check if containerfile %s exists: %v", containerFile, err)
return nil, options, false
}
continue
}
mapping, found := isPathAvailableOnMachine(mounts, vmType, containerFile)
if !found {
logrus.Debugf("Path %s is not available on the running machine", containerFile)
return nil, options, false
}
translatedContainerFiles = append(translatedContainerFiles, mapping.RemotePath)
}
// Additional build contexts
for _, context := range options.AdditionalBuildContexts {
switch {
case context.IsImage, context.IsURL:
continue
default:
if err := fileutils.Lexists(context.Value); errors.Is(err, fs.ErrNotExist) {
logrus.Debugf("Path %s does not exist locally, skipping machine check", context.Value)
return nil, options, false
}
mapping, found := isPathAvailableOnMachine(mounts, vmType, context.Value)
if !found {
logrus.Debugf("Path %s is not available on the running machine", context.Value)
return nil, options, false
}
context.Value = mapping.RemotePath
}
}
return translatedContainerFiles, options, true
}
// IsHyperVProvider checks if the current machine provider is Hyper-V.
// It returns true if the provider is Hyper-V, false otherwise, or an error if the check fails.
func IsHyperVProvider(ctx context.Context) (bool, error) {
conn, err := bindings.GetClient(ctx)
if err != nil {
logrus.Debugf("Failed to get client connection: %v", err)
return false, err
}
_, vmType, err := getMachineMountsAndVMType(conn.URI.String(), conn.URI)
if err != nil {
logrus.Debugf("Failed to get machine mounts: %v", err)
return false, err
}
return vmType == define.HyperVVirt, nil
}
// ValidatePathForLocalAPI checks if the provided path satisfies requirements for local API usage.
// It returns an error if the path is not absolute or does not exist on the filesystem.
func ValidatePathForLocalAPI(path string) error {
if !filepath.IsAbs(path) {
return fmt.Errorf("path %q is not absolute", path)
}
if err := fileutils.Exists(path); err != nil {
return err
}
return nil
}

View File

@@ -5,6 +5,7 @@ package localapi
import (
"context"
"github.com/containers/podman/v5/pkg/domain/entities"
"github.com/sirupsen/logrus"
)
@@ -12,3 +13,18 @@ func CheckPathOnRunningMachine(ctx context.Context, path string) (*LocalAPIMap,
logrus.Debug("CheckPathOnRunningMachine is not supported")
return nil, false
}
func CheckIfImageBuildPathsOnRunningMachine(ctx context.Context, containerFiles []string, options entities.BuildOptions) ([]string, entities.BuildOptions, bool) {
logrus.Debug("CheckIfImageBuildPathsOnRunningMachine is not supported")
return nil, options, false
}
func IsHyperVProvider(ctx context.Context) (bool, error) {
logrus.Debug("IsHyperVProvider is not supported")
return false, nil
}
func ValidatePathForLocalAPI(path string) error {
logrus.Debug("ValidatePathForLocalAPI is not supported")
return nil
}