pkg/testutil/bldr/oci_image.go (113 lines of code) (raw):
package bldr
import (
"archive/tar"
"bytes"
"io"
"os"
"path/filepath"
"slices"
"strings"
"testing"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/tarball"
"github.com/stretchr/testify/require"
)
type OCIImageBuilder struct {
t *testing.T
layers []v1.Layer
}
func OCIImage(t *testing.T) *OCIImageBuilder {
return &OCIImageBuilder{
t: t,
layers: []v1.Layer{},
}
}
func (b *OCIImageBuilder) WithLayer(layer v1.Layer) *OCIImageBuilder {
b.layers = append(b.layers, layer)
return b
}
func (b *OCIImageBuilder) WithFile(path string, content []byte) *OCIImageBuilder {
b.layers = append(b.layers, OCIImageLayer(b.t).WithFile(path, content).Build())
return b
}
func (b *OCIImageBuilder) WithEmptyFile(path string) *OCIImageBuilder {
return b.WithFile(path, []byte{})
}
func (b *OCIImageBuilder) Build() v1.Image {
img := empty.Image
img, err := mutate.AppendLayers(img, b.layers...)
require.NoError(b.t, err)
diffIDs := make([]v1.Hash, len(b.layers))
for i, layer := range b.layers {
diffIDs[i], err = layer.DiffID()
require.NoError(b.t, err)
}
cfg, err := img.ConfigFile()
require.NoError(b.t, err)
cfg.Config.WorkingDir = "/"
cfg.Config.Env = []string{"PATH=/usr/bin"}
cfg.RootFS.DiffIDs = diffIDs
img, err = mutate.ConfigFile(img, cfg)
require.NoError(b.t, err)
return img
}
type fileInfo struct {
content []byte
perm os.FileMode
}
type OCIImageLayerBuilder struct {
t *testing.T
files map[string]fileInfo
}
func OCIImageLayer(t *testing.T) *OCIImageLayerBuilder {
return &OCIImageLayerBuilder{
t: t,
files: make(map[string]fileInfo),
}
}
func (b *OCIImageLayerBuilder) WithFile(path string, fileContent []byte) *OCIImageLayerBuilder {
return b.WithFileWithPerms(path, fileContent, 0644)
}
func (b *OCIImageLayerBuilder) WithFileWithPerms(path string, fileContent []byte, perms os.FileMode) *OCIImageLayerBuilder {
b.files[path] = fileInfo{content: fileContent, perm: perms}
return b
}
func (b *OCIImageLayerBuilder) Build() v1.Layer {
dirsWritten := map[string]struct{}{}
var buf bytes.Buffer
tw := tar.NewWriter(&buf)
for path, info := range b.files {
dirs := b.findUnwrittenDirsInPath(path, dirsWritten)
for _, dir := range dirs {
err := tw.WriteHeader(&tar.Header{Typeflag: tar.TypeDir, Name: dir, Mode: 0777})
require.NoError(b.t, err)
}
err := tw.WriteHeader(&tar.Header{Typeflag: tar.TypeReg, Name: path, Size: int64(len(info.content)), Mode: int64(info.perm)})
require.NoError(b.t, err)
_, err = tw.Write(info.content)
require.NoError(b.t, err)
}
err := tw.Close()
require.NoError(b.t, err)
layer, err := tarball.LayerFromOpener(func() (io.ReadCloser, error) {
return io.NopCloser(bytes.NewReader(buf.Bytes())), nil
})
require.NoError(b.t, err)
return layer
}
func (b *OCIImageLayerBuilder) findUnwrittenDirsInPath(path string, dirsSeen map[string]struct{}) []string {
dirs := make([]string, 0)
dir, _ := filepath.Split(path)
for {
if _, seen := dirsSeen[dir]; seen || len(dir) == 0 || dir == "/" {
break
}
dirs = append(dirs, dir)
dirsSeen[dir] = struct{}{}
dir, _ = filepath.Split(strings.TrimRight(dir, "/"))
}
slices.Reverse(dirs)
return dirs
}