github.com/containers/podman/v4@v4.9.4/pkg/machine/os/ostree.go (about)

     1  //go:build amd64 || arm64
     2  // +build amd64 arm64
     3  
     4  package os
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"os"
    10  	"os/exec"
    11  	"regexp"
    12  	"strings"
    13  
    14  	"github.com/containers/image/v5/transports/alltransports"
    15  	"github.com/sirupsen/logrus"
    16  )
    17  
    18  // OSTree deals with operations on ostree based os's
    19  type OSTree struct { //nolint:revive
    20  }
    21  
    22  // Apply takes an OCI image and does an rpm-ostree rebase on the image
    23  // If no containers-transport is specified,
    24  // apply will first check if the image exists locally, then default to pulling.
    25  // Exec-ing out to rpm-ostree rebase requires sudo, so this means apply cannot
    26  // be called within podman's user namespace if run as rootless.
    27  // This means that we need to export images in containers-storage to oci-dirs
    28  // We also need to do this via an exec, because if we tried to use the ABI functions,
    29  // we would enter the user namespace, the rebase command would fail.
    30  // The pull portion of this function essentially is a work-around for two things:
    31  // 1. rpm-ostree requires you to specify the containers-transport when pulling.
    32  // The pull in podman allows the behavior of os apply to match other podman commands,
    33  // where you only pull if the image does not exist in storage already.
    34  // 2. This works around the root/rootless issue.
    35  // Podman machines are by default set up using a rootless connection.
    36  // rpm-ostree needs to be run as root. If a user wants to use an image in containers-storage,
    37  // rpm-ostree will look at the root storage, and not the user storage, which is unexpected behavior.
    38  // Exporting to an oci-dir works around this, without nagging the user to configure the machine in rootful mode.
    39  func (dist *OSTree) Apply(image string, opts ApplyOptions) error {
    40  	imageWithTransport := image
    41  
    42  	transport := alltransports.TransportFromImageName(image)
    43  
    44  	switch {
    45  	// no transport was specified
    46  	case transport == nil:
    47  		exists, err := execPodmanImageExists(image)
    48  		if err != nil {
    49  			return err
    50  		}
    51  
    52  		if exists {
    53  			fmt.Println("Pulling from", "containers-storage"+":", imageWithTransport)
    54  			dir, err := os.MkdirTemp("", pathSafeString(imageWithTransport))
    55  			if err != nil {
    56  				return err
    57  			}
    58  			if err := os.Chmod(dir, 0755); err != nil {
    59  				return err
    60  			}
    61  
    62  			defer func() {
    63  				if err := os.RemoveAll(dir); err != nil {
    64  					logrus.Errorf("failed to remove temporary pull file: %v", err)
    65  				}
    66  			}()
    67  
    68  			if err := execPodmanSave(dir, image); err != nil {
    69  				return err
    70  			}
    71  
    72  			imageWithTransport = "oci:" + dir
    73  		} else {
    74  			// if image doesn't exist locally, assume that we want to pull and use docker transport
    75  			imageWithTransport = "docker://" + image
    76  		}
    77  	// containers-transport specified
    78  	case transport.Name() == "containers-storage":
    79  		fmt.Println("Pulling from", image)
    80  		dir, err := os.MkdirTemp("", pathSafeString(strings.TrimPrefix(image, "containers-storage"+":")))
    81  		if err != nil {
    82  			return err
    83  		}
    84  
    85  		if err := os.Chmod(dir, 0755); err != nil {
    86  			return err
    87  		}
    88  
    89  		defer func() {
    90  			if err := os.RemoveAll(dir); err != nil {
    91  				logrus.Errorf("failed to remove temporary pull file: %v", err)
    92  			}
    93  		}()
    94  
    95  		if err := execPodmanSave(dir, image); err != nil {
    96  			return err
    97  		}
    98  		imageWithTransport = "oci:" + dir
    99  	}
   100  
   101  	ostreeCli := []string{"rpm-ostree", "--bypass-driver", "rebase", fmt.Sprintf("ostree-unverified-image:%s", imageWithTransport)}
   102  	cmd := exec.Command("sudo", ostreeCli...)
   103  	cmd.Stdout = os.Stdout
   104  	cmd.Stderr = os.Stderr
   105  	return cmd.Run()
   106  }
   107  
   108  // pathSafeString creates a path-safe name for our tmpdirs
   109  func pathSafeString(str string) string {
   110  	alphanumOnly := regexp.MustCompile(`[^a-zA-Z0-9]+`)
   111  
   112  	return alphanumOnly.ReplaceAllString(str, "")
   113  }
   114  
   115  // execPodmanSave execs out to podman save
   116  func execPodmanSave(dir, image string) error {
   117  	saveArgs := []string{"image", "save", "--format", "oci-dir", "-o", dir, image}
   118  
   119  	saveCmd := exec.Command("podman", saveArgs...)
   120  	saveCmd.Stdout = os.Stdout
   121  	saveCmd.Stderr = os.Stderr
   122  	return saveCmd.Run()
   123  }
   124  
   125  // execPodmanSave execs out to podman image exists
   126  func execPodmanImageExists(image string) (bool, error) {
   127  	existsArgs := []string{"image", "exists", image}
   128  
   129  	existsCmd := exec.Command("podman", existsArgs...)
   130  
   131  	if err := existsCmd.Run(); err != nil {
   132  		if exitError, ok := err.(*exec.ExitError); ok {
   133  			switch exitCode := exitError.ExitCode(); exitCode {
   134  			case 1:
   135  				return false, nil
   136  			default:
   137  				return false, errors.New("unable to access local image store")
   138  			}
   139  		}
   140  	}
   141  	return true, nil
   142  }