feat(image): support Podman (fanal#149)

* refactor(daemon): replace Image with DockerImage

* feat(image): support Podman

* chore(mod): update testdocker
This commit is contained in:
Teppei Fukuda
2021-01-19 07:12:30 +02:00
committed by GitHub
parent 3f358815c9
commit 4a10108d11
10 changed files with 359 additions and 135 deletions

2
go.mod
View File

@@ -7,7 +7,7 @@ require (
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 // indirect
github.com/alicebob/miniredis/v2 v2.14.1
github.com/aquasecurity/go-dep-parser v0.0.0-20201028043324-889d4a92b8e0
github.com/aquasecurity/testdocker v0.0.0-20201220111429-5278b43e3eba
github.com/aquasecurity/testdocker v0.0.0-20210106133225-0b17fe083674
github.com/aws/aws-sdk-go v1.27.1
github.com/deckarep/golang-set v1.7.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible

37
go.sum
View File

@@ -50,10 +50,8 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/aquasecurity/go-dep-parser v0.0.0-20201028043324-889d4a92b8e0 h1:cLH3SebzhbJ+jU1GIad8A1N8p7m7OjHhtY6JePISiVc=
github.com/aquasecurity/go-dep-parser v0.0.0-20201028043324-889d4a92b8e0/go.mod h1:X42mTIRhgPalSm81Om2kD+3ydeunbC8TZtZj1bvgRo8=
github.com/aquasecurity/testdocker v0.0.0-20200426142840-5f05bce6f12a h1:hsw7PpiymXP64evn/K7gsj3hWzMqLrdoeE6JkqDocVg=
github.com/aquasecurity/testdocker v0.0.0-20200426142840-5f05bce6f12a/go.mod h1:psfu0MVaiTDLpNxCoNsTeILSKY2EICBwv345f3M+Ffs=
github.com/aquasecurity/testdocker v0.0.0-20201220111429-5278b43e3eba h1:0Tp/eLlMRmBHJFjV3HZImdb//tEzxBGXbI0OXUn6exg=
github.com/aquasecurity/testdocker v0.0.0-20201220111429-5278b43e3eba/go.mod h1:psfu0MVaiTDLpNxCoNsTeILSKY2EICBwv345f3M+Ffs=
github.com/aquasecurity/testdocker v0.0.0-20210106133225-0b17fe083674 h1:Xq/HxWFGaB4G/prC6czH/F5woB91GMCCilJxs/5DnDk=
github.com/aquasecurity/testdocker v0.0.0-20210106133225-0b17fe083674/go.mod h1:psfu0MVaiTDLpNxCoNsTeILSKY2EICBwv345f3M+Ffs=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
@@ -501,39 +499,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEha
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775 h1:TC0v2RSO1u2kn1ZugjrFXkRZAEaqMN/RW+OTZkBzmLE=
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 h1:DvY3Zkh7KabQE/kfzMvYvKirSiguP9Q/veMtkYyf0o8=
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -7,8 +7,17 @@ import (
v1 "github.com/google/go-containerregistry/pkg/v1"
)
func tryDaemon(ref name.Reference) (v1.Image, extender, func(), error) {
img, inspect, cleanup, err := daemon.Image(ref)
func tryDockerDaemon(ref name.Reference) (v1.Image, extender, func(), error) {
img, inspect, cleanup, err := daemon.DockerImage(ref)
if err != nil {
return nil, nil, nil, err
}
return img, daemonExtender{inspect: inspect}, cleanup, nil
}
func tryPodmanDaemon(ref string) (v1.Image, extender, func(), error) {
img, inspect, cleanup, err := daemon.PodmanImage(ref)
if err != nil {
return nil, nil, nil, err
}

50
image/daemon/docker.go Normal file
View File

@@ -0,0 +1,50 @@
package daemon
import (
"context"
"io/ioutil"
"os"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1"
"golang.org/x/xerrors"
)
// DockerImage implements v1.Image by extending daemon.Image.
// The caller must call cleanup() to remove a temporary file.
func DockerImage(ref name.Reference) (v1.Image, *types.ImageInspect, func(), error) {
cleanup := func() {}
c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return nil, nil, cleanup, xerrors.Errorf("failed to initialize a docker client: %w", err)
}
defer func() {
if err != nil {
c.Close()
}
}()
inspect, _, err := c.ImageInspectWithRaw(context.Background(), ref.Name())
if err != nil {
return nil, nil, cleanup, xerrors.Errorf("unable to inspect the image (%s): %w", ref.Name(), err)
}
f, err := ioutil.TempFile("", "fanal-*")
if err != nil {
return nil, nil, cleanup, xerrors.Errorf("failed to create a temporary file")
}
cleanup = func() {
c.Close()
f.Close()
_ = os.Remove(f.Name())
}
return &image{
opener: imageOpener(ref.Name(), f, c.ImageSave),
inspect: inspect,
}, &inspect, cleanup, nil
}

View File

@@ -0,0 +1,52 @@
package daemon
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/docker/docker/api/types"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
)
func TestDockerImage(t *testing.T) {
type fields struct {
Image v1.Image
opener opener
inspect types.ImageInspect
}
tests := []struct {
name string
imageName string
fields fields
want *v1.ConfigFile
wantErr bool
}{
{
name: "happy path",
imageName: "alpine:3.11",
wantErr: false,
},
{
name: "unknown image",
imageName: "alpine:unknown",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ref, err := name.ParseReference(tt.imageName)
require.NoError(t, err)
_, _, cleanup, err := DockerImage(ref)
assert.Equal(t, tt.wantErr, err != nil, err)
defer func() {
if cleanup != nil {
cleanup()
}
}()
})
}
}

View File

@@ -3,13 +3,10 @@ package daemon
import (
"context"
"io"
"io/ioutil"
"os"
"sync"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/tarball"
"golang.org/x/xerrors"
@@ -17,59 +14,14 @@ import (
var mu sync.Mutex
// image is a wrapper for github.com/google/go-containerregistry/pkg/v1/daemon.Image
// daemon.Image loads the entire image into the memory at first,
// but it doesn't need to load it if the information is already in the cache,
// To avoid entire loading, this wrapper uses ImageInspectWithRaw and checks image ID and layer IDs.
type image struct {
v1.Image
opener opener
inspect types.ImageInspect
}
type opener func() (v1.Image, error)
// Image implements v1.Image by extending daemon.Image.
// The caller must call cleanup() to remove a temporary file.
func Image(ref name.Reference) (v1.Image, *types.ImageInspect, func(), error) {
cleanup := func() {}
type imageSave func(context.Context, []string) (io.ReadCloser, error)
c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return nil, nil, cleanup, xerrors.Errorf("failed to initialize a docker client: %w", err)
}
defer func() {
if err != nil {
c.Close()
}
}()
inspect, _, err := c.ImageInspectWithRaw(context.Background(), ref.Name())
if err != nil {
return nil, nil, cleanup, xerrors.Errorf("unable to inspect the image (%s): %w", ref.Name(), err)
}
f, err := ioutil.TempFile("", "fanal-*")
if err != nil {
return nil, nil, cleanup, xerrors.Errorf("failed to create a temporary file")
}
cleanup = func() {
c.Close()
f.Close()
_ = os.Remove(f.Name())
}
return &image{
opener: imageOpener(c, ref, f),
inspect: inspect,
}, &inspect, cleanup, nil
}
func imageOpener(c *client.Client, ref name.Reference, f *os.File) opener {
func imageOpener(ref string, f *os.File, imageSave imageSave) opener {
return func() (v1.Image, error) {
// Store the tarball in local filesystem and return a new reader into the bytes each time we need to access something.
rc, err := c.ImageSave(context.Background(), []string{ref.Name()})
rc, err := imageSave(context.Background(), []string{ref})
if err != nil {
return nil, xerrors.Errorf("unable to export the image: %w", err)
}
@@ -89,6 +41,16 @@ func imageOpener(c *client.Client, ref name.Reference, f *os.File) opener {
}
}
// image is a wrapper for github.com/google/go-containerregistry/pkg/v1/daemon.Image
// daemon.Image loads the entire image into the memory at first,
// but it doesn't need to load it if the information is already in the cache,
// To avoid entire loading, this wrapper uses ImageInspectWithRaw and checks image ID and layer IDs.
type image struct {
v1.Image
opener opener
inspect types.ImageInspect
}
// populateImage initializes an "image" struct.
// This method is called by some goroutines at the same time.
// To prevent multiple heavy initializations, the lock is necessary.
@@ -114,6 +76,13 @@ func (img *image) ConfigName() (v1.Hash, error) {
}
func (img *image) ConfigFile() (*v1.ConfigFile, error) {
if len(img.inspect.RootFS.Layers) == 0 {
// Podman doesn't return RootFS...
if err := img.populateImage(); err != nil {
return nil, xerrors.Errorf("unable to populate: %w", err)
}
return img.Image.ConfigFile()
}
var diffIDs []v1.Hash
for _, l := range img.inspect.RootFS.Layers {
h, err := v1.NewHash(l)

View File

@@ -9,7 +9,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/docker/docker/api/types"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
@@ -22,6 +21,8 @@ func TestMain(m *testing.M) {
"index.docker.io/library/alpine:3.11": "../../test/testdata/alpine-311.tar.gz",
"gcr.io/distroless/base:latest": "../../test/testdata/distroless.tar.gz",
}
// for Docker
opt := engine.Option{
APIVersion: "1.38",
ImagePaths: imagePaths,
@@ -34,46 +35,6 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}
func TestImage(t *testing.T) {
type fields struct {
Image v1.Image
opener opener
inspect types.ImageInspect
}
tests := []struct {
name string
imageName string
fields fields
want *v1.ConfigFile
wantErr bool
}{
{
name: "happy path",
imageName: "alpine:3.11",
wantErr: false,
},
{
name: "unknown image",
imageName: "alpine:unknown",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ref, err := name.ParseReference(tt.imageName)
require.NoError(t, err)
_, _, cleanup, err := Image(ref)
assert.Equal(t, tt.wantErr, err != nil, err)
defer func() {
if cleanup != nil {
cleanup()
}
}()
})
}
}
func Test_image_ConfigName(t *testing.T) {
tests := []struct {
name string
@@ -96,7 +57,7 @@ func Test_image_ConfigName(t *testing.T) {
ref, err := name.ParseReference(tt.imageName)
require.NoError(t, err)
img, _, cleanup, err := Image(ref)
img, _, cleanup, err := DockerImage(ref)
require.NoError(t, err)
defer cleanup()
@@ -156,7 +117,7 @@ func Test_image_ConfigFile(t *testing.T) {
ref, err := name.ParseReference(tt.imageName)
require.NoError(t, err)
img, _, cleanup, err := Image(ref)
img, _, cleanup, err := DockerImage(ref)
require.NoError(t, err)
defer cleanup()
@@ -201,7 +162,7 @@ func Test_image_LayerByDiffID(t *testing.T) {
ref, err := name.ParseReference(tt.imageName)
require.NoError(t, err)
img, _, cleanup, err := Image(ref)
img, _, cleanup, err := DockerImage(ref)
require.NoError(t, err)
defer cleanup()
@@ -230,7 +191,7 @@ func Test_image_RawConfigFile(t *testing.T) {
ref, err := name.ParseReference(tt.imageName)
require.NoError(t, err)
img, _, cleanup, err := Image(ref)
img, _, cleanup, err := DockerImage(ref)
require.NoError(t, err)
defer cleanup()

115
image/daemon/podman.go Normal file
View File

@@ -0,0 +1,115 @@
package daemon
import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"os"
"path/filepath"
"github.com/docker/docker/api/types"
"github.com/google/go-containerregistry/pkg/v1"
"golang.org/x/xerrors"
)
var (
inspectURL = "http://podman/images/%s/json"
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) (types.ImageInspect, error) {
url := fmt.Sprintf(inspectURL, imageName)
resp, err := p.c.Get(url)
if err != nil {
return types.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 types.ImageInspect{}, xerrors.Errorf("unknown status code from Podman: %d", resp.StatusCode)
}
return types.ImageInspect{}, xerrors.New(res.Message)
}
var inspect types.ImageInspect
if err = json.NewDecoder(resp.Body).Decode(&inspect); err != nil {
return types.ImageInspect{}, xerrors.Errorf("unable to decode JSON: %w", err)
}
return inspect, 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
}
// Image implements v1.Image by extending daemon.Image.
// The caller must call cleanup() to remove a temporary file.
func PodmanImage(ref string) (v1.Image, *types.ImageInspect, func(), error) {
cleanup := func() {}
c, err := newPodmanClient()
if err != nil {
return nil, nil, cleanup, xerrors.Errorf("unable to initialize Podman client: %w", err)
}
inspect, err := c.imageInspect(ref)
if err != nil {
return nil, nil, cleanup, xerrors.Errorf("unable to inspect the image (%s): %w", ref, err)
}
f, err := ioutil.TempFile("", "fanal-*")
if err != nil {
return nil, nil, cleanup, xerrors.Errorf("failed to create a temporary file")
}
cleanup = func() {
_ = f.Close()
_ = os.Remove(f.Name())
}
return &image{
opener: imageOpener(ref, f, c.imageSave),
inspect: inspect,
}, &inspect, cleanup, nil
}

View File

@@ -0,0 +1,93 @@
package daemon
import (
"io/ioutil"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"github.com/aquasecurity/testdocker/engine"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/docker/docker/api/types"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
)
func setupPodmanSock(t *testing.T) *httptest.Server {
t.Helper()
runtimeDir, err := ioutil.TempDir("", "daemon")
require.NoError(t, err)
os.Setenv("XDG_RUNTIME_DIR", runtimeDir)
dir := filepath.Join(runtimeDir, "podman")
err = os.MkdirAll(dir, os.ModePerm)
require.NoError(t, err)
sockPath := filepath.Join(dir, "podman.sock")
opt := engine.Option{
APIVersion: "1.40",
ImagePaths: map[string]string{
"index.docker.io/library/alpine:3.11": "../../test/testdata/alpine-311.tar.gz",
},
UnixDomainSocket: sockPath,
}
te := engine.NewDockerEngine(opt)
return te
}
func TestPodmanImage(t *testing.T) {
type fields struct {
Image v1.Image
opener opener
inspect types.ImageInspect
}
tests := []struct {
name string
imageName string
fields fields
wantConfigName string
wantErr bool
}{
{
name: "happy path",
imageName: "alpine:3.11",
wantConfigName: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72",
wantErr: false,
},
{
name: "unknown image",
imageName: "alpine:unknown",
wantErr: true,
},
}
te := setupPodmanSock(t)
defer te.Close()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ref, err := name.ParseReference(tt.imageName)
require.NoError(t, err)
img, _, cleanup, err := PodmanImage(ref.Name())
defer cleanup()
if tt.wantErr {
assert.NotNil(t, err)
return
}
assert.NoError(t, err)
confName, err := img.ConfigName()
require.NoError(t, err)
assert.Equal(t, tt.wantConfigName, confName.String())
})
}
}

View File

@@ -80,13 +80,21 @@ func newDockerImage(ctx context.Context, imageName string, option types.DockerOp
}
// Try accessing Docker Daemon
img, ext, cleanup, err := tryDaemon(ref)
img, ext, cleanup, err := tryDockerDaemon(ref)
if err == nil {
// Return v1.Image if the image is found in Docker Engine
return img, ext, cleanup, nil
}
errs = multierror.Append(errs, err)
// Try accessing Podman
img, ext, cleanup, err = tryPodmanDaemon(imageName)
if err == nil {
// Return v1.Image if the image is found in Podman
return img, ext, cleanup, nil
}
errs = multierror.Append(errs, err)
// Try accessing Docker Registry
img, ext, err = tryRemote(ctx, ref, option)
if err == nil {