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

feat(image): add progress bar for image layer pulling #8186

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
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
8 changes: 8 additions & 0 deletions cmd/trivy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"context"
"errors"
"fmt"
"os"

"golang.org/x/xerrors"
Expand All @@ -21,6 +22,13 @@ func main() {
if errors.As(err, &exitError) {
os.Exit(exitError.Code)
}

var userErr *types.UserError
if errors.As(err, &userErr) {
fmt.Println("Error: " + userErr.Error())
os.Exit(1)
}

log.Fatal("Fatal error", log.Err(err))
}
}
Expand Down
1 change: 1 addition & 0 deletions docs/docs/references/configuration/cli/trivy_image.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ trivy image [flags] IMAGE_NAME
--license-confidence-level float specify license classifier's confidence level (default 0.9)
--license-full eagerly look for licenses in source code headers and license files
--list-all-pkgs output all packages in the JSON report regardless of vulnerability
--max-image-size string maximum image size to process, specified in a human-readable format (e.g., '44kB', '17MB'); an error will be returned if the image exceeds this size
--misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot])
--module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules")
--no-progress suppress progress bar
Expand Down
3 changes: 3 additions & 0 deletions docs/docs/references/configuration/config-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ image:
# Same as '--input'
input: ""

# Same as '--max-image-size'
max-size: ""

# Same as '--platform'
platform: ""

Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ require (
github.com/docker/cli v27.4.1+incompatible
github.com/docker/docker v27.4.1+incompatible
github.com/docker/go-connections v0.5.0
github.com/docker/go-units v0.5.0
github.com/fatih/color v1.18.0
github.com/go-git/go-git/v5 v5.12.0
github.com/go-openapi/runtime v0.28.0 // indirect
Expand All @@ -67,6 +68,7 @@ require (
github.com/hashicorp/hcl/v2 v2.23.0
github.com/hashicorp/terraform-exec v0.21.0
github.com/in-toto/in-toto-golang v0.9.0
github.com/klauspost/compress v1.17.11
github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f
github.com/knqyf263/go-deb-version v0.0.0-20241115132648-6f4aee6ccd23
github.com/knqyf263/go-rpm-version v0.0.0-20220614171824-631e686d1075
Expand Down Expand Up @@ -217,7 +219,6 @@ require (
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.2 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
github.com/dsnet/compress v0.0.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
Expand Down Expand Up @@ -284,7 +285,6 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect
Expand Down
18 changes: 18 additions & 0 deletions integration/docker_engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func TestDockerEngine(t *testing.T) {
ignoreStatus []string
severity []string
ignoreIDs []string
maxImageSize string
input string
golden string
wantErr string
Expand All @@ -34,6 +35,12 @@ func TestDockerEngine(t *testing.T) {
input: "testdata/fixtures/images/alpine-39.tar.gz",
golden: "testdata/alpine-39.json.golden",
},
{
name: "alpine:3.9, with max image size",
maxImageSize: "100mb",
input: "testdata/fixtures/images/alpine-39.tar.gz",
golden: "testdata/alpine-39.json.golden",
},
{
name: "alpine:3.9, with high and critical severity",
severity: []string{
Expand Down Expand Up @@ -195,6 +202,12 @@ func TestDockerEngine(t *testing.T) {
input: "badimage:latest",
wantErr: "unable to inspect the image (badimage:latest)",
},
{
name: "sad path, image size is larger than the maximum",
input: "testdata/fixtures/images/alpine-39.tar.gz",
maxImageSize: "1mb",
wantErr: "uncompressed image size 5.8MB exceeds maximum allowed size 1MB",
},
}

// Set up testing DB
Expand Down Expand Up @@ -263,6 +276,11 @@ func TestDockerEngine(t *testing.T) {
require.NoError(t, err, "failed to write .trivyignore")
defer os.Remove(trivyIgnore)
}

if tt.maxImageSize != "" {
osArgs = append(osArgs, []string{"--max-image-size", tt.maxImageSize}...)
}

osArgs = append(osArgs, tt.input)

// Run Trivy
Expand Down
1 change: 1 addition & 0 deletions pkg/commands/artifact/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,7 @@ func (r *runner) initScannerConfig(ctx context.Context, opts flag.Options) (Scan
Host: opts.PodmanHost,
},
ImageSources: opts.ImageSources,
MaxImageSize: opts.MaxImageSize,
},

// For misconfiguration scanning
Expand Down
85 changes: 85 additions & 0 deletions pkg/fanal/artifact/image/compression.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package image

import (
"bufio"
"bytes"
"compress/gzip"
"errors"
"io"

"github.com/klauspost/compress/zstd"
"golang.org/x/xerrors"

xio "github.com/aquasecurity/trivy/pkg/x/io"
)

// https://en.wikipedia.org/wiki/List_of_file_signatures
var (
gzipMagicNumber = []byte{'\x1f', '\x8b'}
zstdMagicNumber = []byte{'\x28', '\xb5', '\x2f', '\xfd'}
)

type decompressor struct {
magicNumber []byte
wrap func(io.Reader) (io.ReadCloser, error)
}

var decompressors = []decompressor{
{
magicNumber: gzipMagicNumber,
wrap: func(r io.Reader) (io.ReadCloser, error) {
gr, err := gzip.NewReader(r)
if err != nil {
return nil, xerrors.Errorf("failed to create gzip reader: %w", err)
}
return gr, nil
},
},
{
magicNumber: zstdMagicNumber,
wrap: func(r io.Reader) (io.ReadCloser, error) {
zr, err := zstd.NewReader(r)
if err != nil {
return nil, xerrors.Errorf("failed to create zstd reader: %w", err)
}
return zr.IOReadCloser(), nil
},
},
}

// uncompressed checks if the reader contains compressed data and returns the decompressed reader
// or the original reader if the data is not compressed.
func uncompressed(rc io.Reader) (io.ReadCloser, error) {
br := bufio.NewReader(rc)
for _, dec := range decompressors {
ok, err := hasMagicNumber(br, dec.magicNumber)
if err != nil {
return nil, xerrors.Errorf("failed to check file header: %w", err)
}

if ok {
return dec.wrap(br)
}
}

// decompression not required
return &xio.ReadCloser{
Reader: rc,
CloseFunc: func() error { return nil },
}, nil
}

type peekReader interface {
io.Reader
Peek(n int) ([]byte, error)
}

func hasMagicNumber(pr peekReader, magicNumber []byte) (bool, error) {
b, err := pr.Peek(len(magicNumber))
if errors.Is(err, io.EOF) {
return false, nil
} else if err != nil {
return false, err
}
return bytes.Equal(b, magicNumber), nil
}
Loading
Loading