-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
podman.go
144 lines (123 loc) · 3.82 KB
/
podman.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package daemon
import (
"context"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"os"
"path/filepath"
api "github.com/docker/docker/api/types"
dimage "github.com/docker/docker/api/types/image"
"golang.org/x/xerrors"
)
var (
inspectURL = "http://podman/images/%s/json"
historyURL = "http://podman/images/%s/history"
saveURL = "http://podman/images/%s/get"
)
type podmanClient struct {
c http.Client
}
func newPodmanClient() (podmanClient, error) {
// Get Podman socket location
sockDir := os.Getenv("XDG_RUNTIME_DIR")
socket := filepath.Join(sockDir, "podman", "podman.sock")
if _, err := os.Stat(socket); err != nil {
return podmanClient{}, xerrors.Errorf("no podman socket found: %w", err)
}
return podmanClient{
c: http.Client{
Transport: &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", socket)
},
},
},
}, nil
}
type errResponse struct {
Message string
}
func (p podmanClient) imageInspect(imageName string) (api.ImageInspect, error) {
url := fmt.Sprintf(inspectURL, imageName)
resp, err := p.c.Get(url)
if err != nil {
return api.ImageInspect{}, xerrors.Errorf("http error: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
var res errResponse
if err = json.NewDecoder(resp.Body).Decode(&res); err != nil {
return api.ImageInspect{}, xerrors.Errorf("unknown status code from Podman: %d", resp.StatusCode)
}
return api.ImageInspect{}, xerrors.New(res.Message)
}
var inspect api.ImageInspect
if err = json.NewDecoder(resp.Body).Decode(&inspect); err != nil {
return api.ImageInspect{}, xerrors.Errorf("unable to decode JSON: %w", err)
}
return inspect, nil
}
func (p podmanClient) imageHistoryInspect(imageName string) ([]dimage.HistoryResponseItem, error) {
url := fmt.Sprintf(historyURL, imageName)
resp, err := p.c.Get(url)
if err != nil {
return []dimage.HistoryResponseItem{}, xerrors.Errorf("http error: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
var res errResponse
if err = json.NewDecoder(resp.Body).Decode(&res); err != nil {
return []dimage.HistoryResponseItem{}, xerrors.Errorf("unknown status code from Podman: %d", resp.StatusCode)
}
return []dimage.HistoryResponseItem{}, xerrors.New(res.Message)
}
var history []dimage.HistoryResponseItem
if err = json.NewDecoder(resp.Body).Decode(&history); err != nil {
return []dimage.HistoryResponseItem{}, xerrors.Errorf("unable to decode JSON: %w", err)
}
return history, nil
}
func (p podmanClient) imageSave(_ context.Context, imageNames []string) (io.ReadCloser, error) {
if len(imageNames) < 1 {
return nil, xerrors.Errorf("no specified image")
}
url := fmt.Sprintf(saveURL, imageNames[0])
resp, err := p.c.Get(url)
if err != nil {
return nil, xerrors.Errorf("http error: %w", err)
}
return resp.Body, nil
}
// PodmanImage implements v1.Image by extending daemon.Image.
// The caller must call cleanup() to remove a temporary file.
func PodmanImage(ref string) (Image, func(), error) {
cleanup := func() {}
c, err := newPodmanClient()
if err != nil {
return nil, cleanup, xerrors.Errorf("unable to initialize Podman client: %w", err)
}
inspect, err := c.imageInspect(ref)
if err != nil {
return nil, cleanup, xerrors.Errorf("unable to inspect the image (%s): %w", ref, err)
}
history, err := c.imageHistoryInspect(ref)
if err != nil {
return nil, cleanup, xerrors.Errorf("unable to inspect the image (%s): %w", ref, err)
}
f, err := os.CreateTemp("", "fanal-*")
if err != nil {
return nil, cleanup, xerrors.Errorf("failed to create a temporary file")
}
cleanup = func() {
_ = f.Close()
_ = os.Remove(f.Name())
}
return &image{
opener: imageOpener(context.Background(), ref, f, c.imageSave),
inspect: inspect,
history: configHistory(history),
}, cleanup, nil
}