mirror of
https://github.com/containers/skopeo.git
synced 2025-04-28 11:14:08 +00:00
I debated adding "reverse dependency testing" using https://crates.io/crates/containers-image-proxy but I think it's easier to reuse the test infrastructure here. This also starts fleshing out a Go client for the proxy (not that this is going to be something most Go projects would want versus vendoring c/image...but hey, maybe it'll be useful). Now what I hit in trying to use the main test images is currently the proxy fails on manifest lists, so I'll need to fix that. Signed-off-by: Colin Walters <walters@verbum.org>
232 lines
5.1 KiB
Go
232 lines
5.1 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"gopkg.in/check.v1"
|
|
|
|
"github.com/containers/image/v5/manifest"
|
|
)
|
|
|
|
// This image is known to be x86_64 only right now
|
|
const knownNotManifestListedImage_x8664 = "docker://quay.io/coreos/11bot"
|
|
|
|
const expectedProxySemverMajor = "0.2"
|
|
|
|
// request is copied from proxy.go
|
|
// We intentionally copy to ensure that we catch any unexpected "API" changes
|
|
// in the JSON.
|
|
type request struct {
|
|
// Method is the name of the function
|
|
Method string `json:"method"`
|
|
// Args is the arguments (parsed inside the fuction)
|
|
Args []interface{} `json:"args"`
|
|
}
|
|
|
|
// reply is copied from proxy.go
|
|
type reply struct {
|
|
// Success is true if and only if the call succeeded.
|
|
Success bool `json:"success"`
|
|
// Value is an arbitrary value (or values, as array/map) returned from the call.
|
|
Value interface{} `json:"value"`
|
|
// PipeID is an index into open pipes, and should be passed to FinishPipe
|
|
PipeID uint32 `json:"pipeid"`
|
|
// Error should be non-empty if Success == false
|
|
Error string `json:"error"`
|
|
}
|
|
|
|
// maxMsgSize is also copied from proxy.go
|
|
const maxMsgSize = 32 * 1024
|
|
|
|
type proxy struct {
|
|
c *net.UnixConn
|
|
}
|
|
|
|
type pipefd struct {
|
|
// id is the remote identifier "pipeid"
|
|
id uint
|
|
fd *os.File
|
|
}
|
|
|
|
func (self *proxy) call(method string, args []interface{}) (rval interface{}, fd *pipefd, err error) {
|
|
req := request{
|
|
Method: method,
|
|
Args: args,
|
|
}
|
|
reqbuf, err := json.Marshal(&req)
|
|
if err != nil {
|
|
return
|
|
}
|
|
n, err := self.c.Write(reqbuf)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if n != len(reqbuf) {
|
|
err = fmt.Errorf("short write during call of %d bytes", n)
|
|
return
|
|
}
|
|
oob := make([]byte, syscall.CmsgSpace(1))
|
|
replybuf := make([]byte, maxMsgSize)
|
|
n, oobn, _, _, err := self.c.ReadMsgUnix(replybuf, oob)
|
|
if err != nil {
|
|
err = fmt.Errorf("reading reply: %v", err)
|
|
return
|
|
}
|
|
var reply reply
|
|
err = json.Unmarshal(replybuf[0:n], &reply)
|
|
if err != nil {
|
|
err = fmt.Errorf("Failed to parse reply: %w", err)
|
|
return
|
|
}
|
|
if !reply.Success {
|
|
err = fmt.Errorf("remote error: %s", reply.Error)
|
|
return
|
|
}
|
|
|
|
if reply.PipeID > 0 {
|
|
var scms []syscall.SocketControlMessage
|
|
scms, err = syscall.ParseSocketControlMessage(oob[:oobn])
|
|
if err != nil {
|
|
err = fmt.Errorf("failed to parse control message: %v", err)
|
|
return
|
|
}
|
|
if len(scms) != 1 {
|
|
err = fmt.Errorf("Expected 1 received fd, found %d", len(scms))
|
|
return
|
|
}
|
|
var fds []int
|
|
fds, err = syscall.ParseUnixRights(&scms[0])
|
|
if err != nil {
|
|
err = fmt.Errorf("failed to parse unix rights: %v", err)
|
|
return
|
|
}
|
|
fd = &pipefd{
|
|
fd: os.NewFile(uintptr(fds[0]), "replyfd"),
|
|
id: uint(reply.PipeID),
|
|
}
|
|
}
|
|
|
|
rval = reply.Value
|
|
return
|
|
}
|
|
|
|
func newProxy() (*proxy, error) {
|
|
fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_SEQPACKET, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
myfd := os.NewFile(uintptr(fds[0]), "myfd")
|
|
defer myfd.Close()
|
|
theirfd := os.NewFile(uintptr(fds[1]), "theirfd")
|
|
defer theirfd.Close()
|
|
|
|
mysock, err := net.FileConn(myfd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Note ExtraFiles starts at 3
|
|
proc := exec.Command("skopeo", "experimental-image-proxy", "--sockfd", "3")
|
|
proc.Stderr = os.Stderr
|
|
proc.SysProcAttr = &syscall.SysProcAttr{
|
|
Pdeathsig: syscall.SIGTERM,
|
|
}
|
|
proc.ExtraFiles = append(proc.ExtraFiles, theirfd)
|
|
|
|
if err = proc.Start(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &proxy{
|
|
c: mysock.(*net.UnixConn),
|
|
}, nil
|
|
}
|
|
|
|
func init() {
|
|
check.Suite(&ProxySuite{})
|
|
}
|
|
|
|
type ProxySuite struct {
|
|
}
|
|
|
|
func (s *ProxySuite) SetUpSuite(c *check.C) {
|
|
}
|
|
|
|
func (s *ProxySuite) TearDownSuite(c *check.C) {
|
|
}
|
|
|
|
func initOci(p string) error {
|
|
err := ioutil.WriteFile(filepath.Join(p, "oci-layout"), []byte("{\"imageLayoutVersion\":\"1.0.0\"}"), 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
blobdir := filepath.Join(p, "blobs/sha256")
|
|
err = os.MkdirAll(blobdir, 0755)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type byteFetch struct {
|
|
content []byte
|
|
err error
|
|
}
|
|
|
|
func (s *ProxySuite) TestProxy(c *check.C) {
|
|
p, err := newProxy()
|
|
c.Assert(err, check.IsNil)
|
|
|
|
v, fd, err := p.call("Initialize", nil)
|
|
c.Assert(err, check.IsNil)
|
|
semver, ok := v.(string)
|
|
if !ok {
|
|
c.Fatalf("Unexpected value %T", v)
|
|
}
|
|
if !strings.HasPrefix(semver, expectedProxySemverMajor) {
|
|
c.Fatalf("Unexpected semver %s", semver)
|
|
}
|
|
c.Assert(fd, check.IsNil)
|
|
|
|
v, fd, err = p.call("OpenImage", []interface{}{knownNotManifestListedImage_x8664})
|
|
c.Assert(err, check.IsNil)
|
|
c.Assert(fd, check.IsNil)
|
|
|
|
imgidv, ok := v.(float64)
|
|
c.Assert(ok, check.Equals, true)
|
|
imgid := uint32(imgidv)
|
|
|
|
v, fd, err = p.call("GetManifest", []interface{}{imgid})
|
|
c.Assert(err, check.IsNil)
|
|
c.Assert(fd, check.NotNil)
|
|
fetchchan := make(chan byteFetch)
|
|
go func() {
|
|
manifestBytes, err := ioutil.ReadAll(fd.fd)
|
|
fetchchan <- byteFetch{
|
|
content: manifestBytes,
|
|
err: err,
|
|
}
|
|
}()
|
|
_, _, err = p.call("FinishPipe", []interface{}{fd.id})
|
|
c.Assert(err, check.IsNil)
|
|
fetchRes := <-fetchchan
|
|
c.Assert(fetchRes.err, check.IsNil)
|
|
|
|
_, err = manifest.OCI1FromManifest(fetchRes.content)
|
|
c.Assert(err, check.IsNil)
|
|
|
|
td, err := ioutil.TempDir("", "skopeo-proxy")
|
|
defer os.RemoveAll(td)
|
|
|
|
c.Assert(initOci(td), check.IsNil)
|
|
}
|