package testutil import ( "context" "encoding/json" "io" "os" "strings" "testing" "github.com/docker/docker/api/types/image" "github.com/docker/docker/client" "github.com/google/go-containerregistry/pkg/v1/tarball" "github.com/stretchr/testify/require" gzutil "github.com/aquasecurity/trivy/pkg/fanal/utils/gzip" ) type DockerClient struct { *client.Client } func NewDockerClient(t *testing.T) *DockerClient { cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) require.NoError(t, err) return &DockerClient{Client: cli} } // ImageLoad loads a Docker image from a tar archive file into the Docker engine. // It automatically registers cleanup via t.Cleanup() to remove the loaded image after the test. func (c *DockerClient) ImageLoad(t *testing.T, ctx context.Context, imageFile string) string { t.Helper() testfile, err := os.Open(imageFile) require.NoError(t, err) defer testfile.Close() // Load image into docker engine res, err := c.Client.ImageLoad(ctx, testfile, client.ImageLoadWithQuiet(true)) require.NoError(t, err) defer res.Body.Close() // Parse the response and extract the loaded image name var data struct { Stream string `json:"stream"` } err = json.NewDecoder(res.Body).Decode(&data) require.NoError(t, err) loadedImage := strings.TrimPrefix(data.Stream, "Loaded image: ") loadedImage = strings.TrimSpace(loadedImage) require.NotEmpty(t, loadedImage, data.Stream) // Register cleanup to remove the loaded image after the test t.Cleanup(func() { c.ImageRemove(t, ctx, loadedImage) }) return loadedImage } func (c *DockerClient) ImageRemove(t *testing.T, ctx context.Context, imageID string) { t.Helper() _, _ = c.Client.ImageRemove(ctx, imageID, image.RemoveOptions{ Force: true, PruneChildren: true, }) } // ImageCleanLoad performs a clean load of a Docker image from a tar archive. // It removes any existing images with conflicting RepoTags before loading, // ensuring the loaded image has the correct RepoTags from the archive. // It automatically registers cleanup via t.Cleanup() to remove the loaded image after the test. func (c *DockerClient) ImageCleanLoad(t *testing.T, ctx context.Context, archivePath string) string { t.Helper() // Extract RepoTags from archive opener := func() (io.ReadCloser, error) { return gzutil.OpenFile(archivePath) } manifest, err := tarball.LoadManifest(opener) require.NoError(t, err, "failed to load manifest from archive") // Remove existing images with the same RepoTags to avoid conflicts for _, m := range manifest { for _, tag := range m.RepoTags { c.ImageRemove(t, ctx, tag) } } // Load image return c.ImageLoad(t, ctx, archivePath) }