Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: allow bom build and verification for build_only layers #609

Merged
merged 2 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions pkg/stacker/bom.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import (
"fmt"
"io"
"io/fs"
"os"
"path"
"path/filepath"

"github.com/pkg/errors"
"stackerbuild.io/stacker/pkg/container"
"stackerbuild.io/stacker/pkg/log"
"stackerbuild.io/stacker/pkg/types"
Expand Down Expand Up @@ -109,6 +111,11 @@
// if a bom is available, add it here so it can be merged
srcpath := path.Join(sc.StackerDir, "artifacts", src.Tag, fmt.Sprintf("%s.json", src.Tag))

_, err := os.Lstat(srcpath)
if err != nil && errors.Is(err, fs.ErrNotExist) {
return nil
}

Check warning on line 117 in pkg/stacker/bom.go

View check run for this annotation

Codecov / codecov/patch

pkg/stacker/bom.go#L114-L117

Added lines #L114 - L117 were not covered by tests

dstfp, err := os.CreateTemp(path.Join(sc.StackerDir, "artifacts", name), fmt.Sprintf("%s-*.json", name))
if err != nil {
return err
Expand Down
47 changes: 24 additions & 23 deletions pkg/stacker/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,30 @@
}
}

// build artifacts such as BOMs, etc
if l.Bom != nil && l.Bom.Generate {
log.Debugf("generating layer artifacts for %s", name)

if err := ImportArtifacts(opts.Config, l.From, name); err != nil {
log.Errorf("unable to import previously built artifacts, err:%v", err)
return err
}

Check warning on line 519 in pkg/stacker/build.go

View check run for this annotation

Codecov / codecov/patch

pkg/stacker/build.go#L517-L519

Added lines #L517 - L519 were not covered by tests

for _, pkg := range l.Bom.Packages {
if err := BuildLayerArtifacts(opts.Config, s, l, name, pkg); err != nil {
log.Errorf("failed to generate layer artifacts for %s - %v", name, err)
return err
}

Check warning on line 525 in pkg/stacker/build.go

View check run for this annotation

Codecov / codecov/patch

pkg/stacker/build.go#L523-L525

Added lines #L523 - L525 were not covered by tests
}

if err := VerifyLayerArtifacts(opts.Config, s, l, name); err != nil {
log.Errorf("failed to validate layer artifacts for %s - %v", name, err)
// the generated bom is invalid, remove it so we don't publish it and also get a cache miss for rebuilds
_ = os.Remove(path.Join(opts.Config.StackerDir, "artifacts", name, fmt.Sprintf("%s.json", name)))
return err
}

Check warning on line 533 in pkg/stacker/build.go

View check run for this annotation

Codecov / codecov/patch

pkg/stacker/build.go#L529-L533

Added lines #L529 - L533 were not covered by tests
}

// This is a build only layer, meaning we don't need to include
// it in the final image, as outputs from it are going to be
// imported into future images. Let's just snapshot it and add
Expand Down Expand Up @@ -554,29 +578,6 @@

log.Infof("filesystem %s built successfully", name)

// build artifacts such as BOMs, etc
if l.Bom != nil && l.Bom.Generate {
log.Debugf("generating layer artifacts for %s", name)

if err := ImportArtifacts(opts.Config, l.From, name); err != nil {
log.Errorf("unable to import previously built artifacts, err:%v", err)
return err
}

for _, pkg := range l.Bom.Packages {
if err := BuildLayerArtifacts(opts.Config, s, l, name, pkg); err != nil {
log.Errorf("failed to generate layer artifacts for %s - %v", name, err)
return err
}
}

if err := VerifyLayerArtifacts(opts.Config, s, l, name); err != nil {
log.Errorf("failed to validate layer artifacts for %s - %v", name, err)
// the generated bom is invalid, remove it so we don't publish it and also get a cache miss for rebuilds
_ = os.Remove(path.Join(opts.Config.StackerDir, "artifacts", name, fmt.Sprintf("%s.json", name)))
return err
}
}
}

return oci.GC(context.Background())
Expand Down
146 changes: 146 additions & 0 deletions test/bom.bats
Original file line number Diff line number Diff line change
Expand Up @@ -305,3 +305,149 @@ EOF
fi
stacker clean
}

@test "all container contents must be accounted for even for build_only layers" {
skip_slow_test
cat > stacker.yaml <<"EOF"
bom-parent:
build_only: true
from:
type: oci
url: ${{CENTOS_OCI}}
bom:
generate: true
namespace: "https://test.io/artifacts"
packages:
- name: pkg1
version: 1.0.0
license: Apache-2.0
paths: [/pkg1]
- name: pkg2
version: 1.0.0
license: Apache-2.0
paths: [/pkg2]
run: |
# discover installed pkgs
/stacker/bin/stacker bom discover
# our own custom packages
mkdir -p /pkg1
touch /pkg1/file
mkdir -p /pkg2
touch /pkg2/file
# should cause build to fail!
mkdir -p /orphan-without-a-package
touch /orphan-without-a-package/file
# cleanup
rm -rf /var/lib/alternatives /tmp/* \
/etc/passwd- /etc/group- /etc/shadow- /etc/gshadow- \
/etc/sysconfig/network /etc/nsswitch.conf.bak \
/etc/rpm/macros.image-language-conf /var/lib/rpm/.dbenv.lock \
/var/lib/rpm/Enhancename /var/lib/rpm/Filetriggername \
/var/lib/rpm/Recommendname /var/lib/rpm/Suggestname \
/var/lib/rpm/Supplementname /var/lib/rpm/Transfiletriggername \
/var/log/anaconda \
/etc/sysconfig/anaconda /etc/sysconfig/network-scripts/ifcfg-* \
/etc/sysconfig/sshd-permitrootlogin /root/anaconda-* /root/original-* /run/nologin \
/var/lib/rpm/.rpm.lock /etc/.pwd.lock /etc/BUILDTIME
annotations:
org.opencontainers.image.authors: bom-test
org.opencontainers.image.vendor: bom-test
org.opencontainers.image.licenses: MIT
EOF
run stacker build --substitute CENTOS_OCI=${CENTOS_OCI}
[ "$status" -ne 0 ]
# a full inventory for this image
[ -f .stacker/artifacts/bom-parent/inventory.json ]
# sbom for this image shouldn't be generated
[ ! -a .stacker/artifacts/bom-parent/first.json ]
# building a second time also fails due to missed cache
run stacker build
[ "$status" -ne 0 ]
stacker clean
}

@test "skip bom generation for built layer" {
skip_slow_test
cat > stacker.yaml <<"EOF"
first:
from:
type: oci
url: ${{CENTOS_OCI}}
bom:
generate: true
namespace: "https://test.io/artifacts"
packages:
- name: pkg1
version: 1.0.0
license: Apache-2.0
paths: [/pkg1]
- name: pkg2
version: 1.0.0
license: Apache-2.0
paths: [/pkg2]
run: |
# our own custom packages
mkdir -p /pkg1
touch /pkg1/file
mkdir -p /pkg2
touch /pkg2/file

second:
from:
type: built
tag: first
bom:
generate: true
namespace: "https://test.io/artifacts"
packages:
- name: pkg1
version: 1.0.0
license: Apache-2.0
paths: [/pkg1]
- name: pkg2
version: 1.0.0
license: Apache-2.0
paths: [/pkg2]
- name: pkg3
version: 1.0.0
license: Apache-2.0
paths: [/pkg3]
run: |
# discover installed pkgs
/stacker/bin/stacker bom discover
# our own custom packages
mkdir -p /pkg3
touch /pkg3/file
# cleanup
rm -rf /var/lib/alternatives /tmp/* \
/etc/passwd- /etc/group- /etc/shadow- /etc/gshadow- \
/etc/sysconfig/network /etc/nsswitch.conf.bak \
/etc/rpm/macros.image-language-conf /var/lib/rpm/.dbenv.lock \
/var/lib/rpm/Enhancename /var/lib/rpm/Filetriggername \
/var/lib/rpm/Recommendname /var/lib/rpm/Suggestname \
/var/lib/rpm/Supplementname /var/lib/rpm/Transfiletriggername \
/var/log/anaconda \
/etc/sysconfig/anaconda /etc/sysconfig/network-scripts/ifcfg-* \
/etc/sysconfig/sshd-permitrootlogin /root/anaconda-* /root/original-* /run/nologin \
/var/lib/rpm/.rpm.lock /etc/.pwd.lock /etc/BUILDTIME
annotations:
org.opencontainers.image.authors: bom-test
org.opencontainers.image.vendor: bom-test
org.opencontainers.image.licenses: MIT
EOF
stacker build --substitute CENTOS_OCI=${CENTOS_OCI}
# a full inventory for this image
[ -f .stacker/artifacts/second/inventory.json ]
# sbom for this image
[ -f .stacker/artifacts/second/second.json ]
if [ -n "${ZOT_HOST}:${ZOT_PORT}" ]; then
zot_setup
stacker publish --skip-tls --url docker://${ZOT_HOST}:${ZOT_PORT} --tag latest --substitute CENTOS_OCI=${CENTOS_OCI}
refs=$(regctl artifact tree ${ZOT_HOST}:${ZOT_PORT}/second:latest --format "{{json .}}" | jq '.referrer | length')
[ $refs -eq 2 ]
refs=$(regctl artifact get --subject ${ZOT_HOST}:${ZOT_PORT}/second:latest --filter-artifact-type "application/spdx+json" | jq '.SPDXID')
[ $refs == \"SPDXRef-DOCUMENT\" ]
zot_teardown
fi
stacker clean
}