Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/container_backend/docker_server_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ func (backend *DockerServerBackend) GenerateSBOM(ctx context.Context, scanOpts s
})
contextAddFiles = append(contextAddFiles, workingTree.Containerfile())

archive := newSbomContextArchiver(workingTree.RootDir())
archive := NewSbomContextArchiver(workingTree.RootDir())

if err := archive.Create(ctx, BuildContextArchiveCreateOptions{
DockerfileRelToContextPath: workingTree.Containerfile(),
Expand Down
29 changes: 12 additions & 17 deletions pkg/container_backend/sbom_build_context_archiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,23 @@ import (
"archive/tar"
"context"
"fmt"
"io"
"os"
"path/filepath"
)

type sbomBuildContextArchiver struct {
type SbomBuildContextArchiver struct {
rootDir string
tarPath string
}

func newSbomContextArchiver(rootDir string) *sbomBuildContextArchiver {
return &sbomBuildContextArchiver{
func NewSbomContextArchiver(rootDir string) *SbomBuildContextArchiver {
return &SbomBuildContextArchiver{
rootDir: rootDir,
}
}

func (a *sbomBuildContextArchiver) Create(_ context.Context, opts BuildContextArchiveCreateOptions) error {
func (a *SbomBuildContextArchiver) Create(_ context.Context, opts BuildContextArchiveCreateOptions) error {
tarFile, err := os.Create(filepath.Join(a.rootDir, "sbom-docker.tar"))
if err != nil {
return fmt.Errorf("unable to create tar file: %w", err)
Expand Down Expand Up @@ -50,14 +51,8 @@ func (a *sbomBuildContextArchiver) Create(_ context.Context, opts BuildContextAr
return fmt.Errorf("unable to write tar header %v: %w", hdr, err)
}

fileContent := make([]byte, stat.Size())

if _, err = file.Read(fileContent); err != nil {
return fmt.Errorf("unable to read file %q: %w", file.Name(), err)
}

if _, err = tarWriter.Write(fileContent); err != nil {
return fmt.Errorf("unable to write to tar %q: %w", tarFile.Name(), err)
if _, err = io.Copy(tarWriter, file); err != nil {
return fmt.Errorf("unable to copy file %q: %w", file.Name(), err)
}

if err = file.Close(); err != nil {
Expand All @@ -72,21 +67,21 @@ func (a *sbomBuildContextArchiver) Create(_ context.Context, opts BuildContextAr
return nil
}

func (a *sbomBuildContextArchiver) Path() string {
func (a *SbomBuildContextArchiver) Path() string {
return a.tarPath
}

func (a *sbomBuildContextArchiver) ExtractOrGetExtractedDir(ctx context.Context) (string, error) {
func (a *SbomBuildContextArchiver) ExtractOrGetExtractedDir(ctx context.Context) (string, error) {
return "", nil
}

func (a *sbomBuildContextArchiver) CalculatePathsChecksum(ctx context.Context, paths []string) (string, error) {
func (a *SbomBuildContextArchiver) CalculatePathsChecksum(ctx context.Context, paths []string) (string, error) {
return "", nil
}

func (a *sbomBuildContextArchiver) CalculateGlobsChecksum(ctx context.Context, globs []string, checkForArchive bool) (string, error) {
func (a *SbomBuildContextArchiver) CalculateGlobsChecksum(ctx context.Context, globs []string, checkForArchive bool) (string, error) {
return "", nil
}

func (a *sbomBuildContextArchiver) CleanupExtractedDir(ctx context.Context) {
func (a *SbomBuildContextArchiver) CleanupExtractedDir(ctx context.Context) {
}
121 changes: 121 additions & 0 deletions pkg/container_backend/sbom_build_context_archiver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package container_backend_test

import (
"archive/tar"
"bytes"
"errors"
"io"
"os"
"path/filepath"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/types"

"github.com/werf/werf/v2/pkg/container_backend"
)

var _ = Describe("SbomBuildContextArchiver", func() {
DescribeTable("Create() should",
func(ctx SpecContext, tc testCase) {
tmpDir := GinkgoT().TempDir()

// arrange: create input files
prepareFixtures(tmpDir, tc.fixtures)

archiver := container_backend.NewSbomContextArchiver(tmpDir)

err := archiver.Create(ctx, container_backend.BuildContextArchiveCreateOptions{
ContextAddFiles: tc.contextAdd,
})
Expect(err).To(tc.createErrMatcher)

Expect(archiver.Path()).To(Equal(filepath.Join(tmpDir, "sbom-docker.tar")))

verifyArchive(tmpDir, tc)
},
Entry("successfully creates tar and adds files from mode 0600", testCase{
fixtures: map[string][]byte{
"foo.txt": []byte("hello"),
"dir/bar.json": []byte(`{"x":1}`),
"empty.bin": []byte(""),
"dir/nested.md": []byte("# t"),
},
contextAdd: []string{
"foo.txt",
"dir/bar.json",
"empty.bin",
},
createErrMatcher: Succeed(),
verifyArchive: true,
verifyEntries: map[string][]byte{
"foo.txt": []byte("hello"),
"dir/bar.json": []byte(`{"x":1}`),
"empty.bin": []byte(""),
},
}),
Entry("error if the added file is missing", testCase{
fixtures: map[string][]byte{
"exists.txt": []byte("ok"),
},
contextAdd: []string{
"exists.txt",
"nope.txt",
},
createErrMatcher: MatchError(ContainSubstring("nope.txt: no such file or directory")),
verifyArchive: false,
}),
)
})

type testCase struct {
fixtures map[string][]byte // path -> content (relative to root)
contextAdd []string
createErrMatcher types.GomegaMatcher
verifyArchive bool
verifyEntries map[string][]byte // name -> content
}

func prepareFixtures(tmpDir string, fixtures map[string][]byte) {
// arrange: create input files
for rel, content := range fixtures {
abs := filepath.Join(tmpDir, rel)
Expect(os.MkdirAll(filepath.Dir(abs), 0o755)).To(Succeed())
Expect(os.WriteFile(abs, content, 0o644)).To(Succeed())
}
}

func verifyArchive(tmpDir string, tc testCase) {
if tc.verifyArchive {
f, openErr := os.Open(filepath.Join(tmpDir, "sbom-docker.tar"))
Expect(openErr).NotTo(HaveOccurred())
defer f.Close()

tr := tar.NewReader(f)
seen := map[string]struct{}{}

for {
hdr, rErr := tr.Next()
if errors.Is(rErr, io.EOF) {
break
}
Expect(rErr).NotTo(HaveOccurred())

seen[hdr.Name] = struct{}{}

Expect(hdr.Mode).To(Equal(int64(0o600)))

if want, ok := tc.verifyEntries[hdr.Name]; ok {
got, readErr := io.ReadAll(tr)
Expect(readErr).NotTo(HaveOccurred())
Expect(bytes.Equal(got, want)).To(BeTrue(), "content mismatch for %s", hdr.Name)
Expect(hdr.Size).To(Equal(int64(len(want))))
}
}

for name := range tc.verifyEntries {
_, ok := seen[name]
Expect(ok).To(BeTrue(), "expected tar entry %q to exist", name)
}
}
}
Loading