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 }