Skip to content

Commit 6403819

Browse files
authored
Merge pull request #129 from thin-edge/feat-support-older-podman-pull-images
feat: try pulling images using podman api directly
2 parents 03168aa + 11fd524 commit 6403819

File tree

2 files changed

+120
-9
lines changed

2 files changed

+120
-9
lines changed

pkg/container/container.go

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -558,13 +558,37 @@ func (c *ContainerClient) ImagePullWithRetries(ctx context.Context, imageRef str
558558

559559
// Note: ImagePull does not seem to return an error if the private registries authentication fails
560560
// so after pulling the image, check if it is loaded to confirm everything worked as expected
561-
out, err := c.Client.ImagePull(ctx, imageRef, pullOptions)
562-
if err != nil {
563-
return nil, err
561+
useDockerPull := false
562+
563+
// try podman api first and but fallback to docker pull API fails
564+
// Note: Podman 4.4 was observed to have an issue pulling images via the docker API where the only reported error is:
565+
// "write /dev/stderr: input/output error"
566+
podmanLib := NewDefaultLibPodHTTPClient()
567+
if podmanLib.Test(ctx) == nil {
568+
slog.Info("Using podman API to pull image")
569+
libpodErr := podmanLib.PullImages(ctx, imageRef, alwaysPull, PodmanPullOptions{
570+
PullOptions: pullOptions,
571+
Quiet: false,
572+
})
573+
574+
// Don't fail as it is unclear how stable the libpod API is
575+
// and a check for the image is done afterwards anyway
576+
if libpodErr != nil {
577+
slog.Warn("podman (libpod) pull images failed but error will be ignored.", "err", libpodErr)
578+
useDockerPull = true
579+
}
564580
}
565-
defer out.Close()
566-
if _, ioErr := io.Copy(os.Stderr, out); ioErr != nil {
567-
slog.Warn("Could not write to stderr.", "err", ioErr)
581+
582+
if useDockerPull {
583+
slog.Info("Trying to pull image using docker API")
584+
out, err := c.Client.ImagePull(ctx, imageRef, pullOptions)
585+
if err != nil {
586+
return nil, err
587+
}
588+
defer out.Close()
589+
if _, ioErr := io.Copy(os.Stderr, out); ioErr != nil {
590+
slog.Warn("Could not write to stderr.", "err", ioErr)
591+
}
568592
}
569593

570594
//
@@ -1234,12 +1258,12 @@ func (c *ContainerClient) ImagesPruneUnused(ctx context.Context) (image.PruneRep
12341258

12351259
// Note: Due to a bug in podman <= 4.8, the above call will fail, so the direct libpod is used instead
12361260
// Reference: https://github.com/containers/podman/issues/20469
1261+
// NewDefaultLibPodHTTPClient
1262+
12371263
slog.Info("Prune images failed. This is expected when using podman < 4.8.", "err", apiErr)
12381264

12391265
slog.Info("Using podman api to prune unused images")
1240-
socketAddr := findContainerEngineSocket()
1241-
libpod := NewLibPodHTTPClient(socketAddr)
1242-
report, libpodErr := libpod.PruneImages(nil)
1266+
report, libpodErr := NewDefaultLibPodHTTPClient().PruneImages(nil)
12431267

12441268
// Don't fail as it is unclear how stable the libpod api is
12451269
if libpodErr != nil {

pkg/container/socket_client.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@ package container
33
import (
44
"context"
55
"encoding/json"
6+
"errors"
7+
"fmt"
68
"io"
9+
"log/slog"
710
"net"
811
"net/http"
12+
"os"
913
"strings"
1014

1115
"github.com/docker/docker/api/types/image"
16+
"github.com/google/go-querystring/query"
1217
)
1318

1419
type ResponsePruneImage struct {
@@ -21,6 +26,10 @@ type SocketClient struct {
2126
Client *http.Client
2227
}
2328

29+
func NewDefaultLibPodHTTPClient() *SocketClient {
30+
return NewLibPodHTTPClient(findContainerEngineSocket())
31+
}
32+
2433
func NewLibPodHTTPClient(sock string) *SocketClient {
2534
httpc := http.Client{
2635
Transport: &http.Transport{
@@ -40,6 +49,19 @@ func (c *SocketClient) resolveURL(path string) string {
4049
return strings.Join([]string{c.BaseURL, strings.TrimPrefix(path, "/")}, "/")
4150
}
4251

52+
var ErrPodmanAPIError = errors.New("podman api not available")
53+
54+
func (c *SocketClient) Test(ctx context.Context) error {
55+
r, err := c.Client.Get(c.resolveURL("info"))
56+
if err != nil {
57+
return err
58+
}
59+
if r.StatusCode != 200 {
60+
return ErrPodmanAPIError
61+
}
62+
return nil
63+
}
64+
4365
// Prune all images and return object in same format as the docker prune response
4466
func (c *SocketClient) PruneImages(body io.Reader) (report image.PruneReport, err error) {
4567
r, err := c.Client.Post(c.resolveURL("images/prune?all=true"), "application/json", body)
@@ -69,3 +91,68 @@ func (c *SocketClient) PruneImages(body io.Reader) (report image.PruneReport, er
6991
report.SpaceReclaimed = spaceReclaimed
7092
return
7193
}
94+
95+
type PodmanAPIPullOptions struct {
96+
AllTags *bool `url:"allTags,omitempty"`
97+
Quiet *bool `url:"quiet,omitempty"`
98+
Policy string `url:"policy,omitempty"`
99+
Reference string `url:"reference"`
100+
}
101+
102+
func (po *PodmanAPIPullOptions) WithPolicy(v string) *PodmanAPIPullOptions {
103+
po.Policy = v
104+
return po
105+
}
106+
107+
func (po *PodmanAPIPullOptions) WithAllTags(v bool) *PodmanAPIPullOptions {
108+
po.AllTags = &v
109+
return po
110+
}
111+
112+
func (po *PodmanAPIPullOptions) WithQuiet(v bool) *PodmanAPIPullOptions {
113+
po.Quiet = &v
114+
return po
115+
}
116+
117+
type PodmanPullOptions struct {
118+
image.PullOptions
119+
120+
Quiet bool
121+
}
122+
123+
func (c *SocketClient) PullImages(ctx context.Context, imageRef string, alwaysPull bool, pullOptions PodmanPullOptions) error {
124+
options := PodmanAPIPullOptions{
125+
Reference: imageRef,
126+
}
127+
options.WithQuiet(pullOptions.Quiet)
128+
if alwaysPull {
129+
options.WithPolicy("always")
130+
}
131+
132+
queryParams, err := query.Values(options)
133+
if err != nil {
134+
return err
135+
}
136+
137+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.resolveURL(fmt.Sprintf("images/pull?%s", queryParams.Encode())), nil)
138+
if err != nil {
139+
return err
140+
}
141+
142+
if pullOptions.RegistryAuth != "" {
143+
req.Header.Set("X-Registry-Auth", pullOptions.RegistryAuth)
144+
}
145+
146+
r, err := c.Client.Do(req)
147+
if err != nil {
148+
return err
149+
}
150+
151+
defer r.Body.Close()
152+
if _, ioErr := io.Copy(os.Stderr, r.Body); ioErr != nil {
153+
slog.Warn("Could not write to stderr.", "err", ioErr)
154+
}
155+
156+
slog.Info("Podman API response was successful.", "status", r.Status)
157+
return nil
158+
}

0 commit comments

Comments
 (0)