mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-06 04:41:18 -08:00
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:
2
go.mod
2
go.mod
@@ -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
37
go.sum
@@ -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=
|
||||
|
||||
@@ -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
50
image/daemon/docker.go
Normal 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
|
||||
}
|
||||
52
image/daemon/docker_test.go
Normal file
52
image/daemon/docker_test.go
Normal 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()
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
115
image/daemon/podman.go
Normal 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
|
||||
}
|
||||
93
image/daemon/podman_test.go
Normal file
93
image/daemon/podman_test.go
Normal 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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user