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

Refactor image extraction progress to use option #13

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1143,6 +1143,11 @@ Ignore /var/run when taking image snapshot. Set it to false to preserve
Set this flag as `--ignore-path=<path>` to ignore path when taking an image
snapshot. Set it multiple times for multiple ignore paths.

#### Flag `--image-fs-extract-progres`

Set this flag to show the progress of extracting an image filesystem. Defaults
to `false`.

#### Flag `--image-fs-extract-retry`

Set this flag to the number of retries that should happen for the extracting an
Expand Down
1 change: 1 addition & 0 deletions cmd/executor/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ func addKanikoOptionsFlags() {
RootCmd.PersistentFlags().BoolVarP(&opts.SkipTLSVerifyPull, "skip-tls-verify-pull", "", false, "Pull from insecure registry ignoring TLS verify")
RootCmd.PersistentFlags().IntVar(&opts.PushRetry, "push-retry", 0, "Number of retries for the push operation")
RootCmd.PersistentFlags().BoolVar(&opts.PushIgnoreImmutableTagErrors, "push-ignore-immutable-tag-errors", false, "If true, known tag immutability errors are ignored and the push finishes with success.")
RootCmd.PersistentFlags().BoolVar(&opts.ImageFSExtractProgress, "image-fs-extract-progress", false, "Show progress for image FS extraction")
RootCmd.PersistentFlags().IntVar(&opts.ImageFSExtractRetry, "image-fs-extract-retry", 0, "Number of retries for image FS extraction")
RootCmd.PersistentFlags().IntVar(&opts.ImageDownloadRetry, "image-download-retry", 0, "Number of retries for downloading the remote image")
RootCmd.PersistentFlags().StringVarP(&opts.KanikoDir, "kaniko-dir", "", constants.DefaultKanikoPath, "Path to the kaniko directory, this takes precedence over the KANIKO_DIR environment variable.")
Expand Down
1 change: 1 addition & 0 deletions pkg/config/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ type KanikoOptions struct {
OCILayoutPath string
Compression Compression
CompressionLevel int
ImageFSExtractProgress bool
ImageFSExtractRetry int
SingleSnapshot bool
Reproducible bool
Expand Down
8 changes: 4 additions & 4 deletions pkg/executor/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ func (s *stageBuilder) build() error {
t := timing.Start("FS Unpacking")

retryFunc := func() error {
_, err := getFSFromImage(config.RootDir, s.image, util.ExtractFile)
_, err := getFSFromImage(config.RootDir, s.image, util.ExtractFile, s.opts.ImageFSExtractProgress)
return err
}

Expand Down Expand Up @@ -939,7 +939,7 @@ func fetchExtraStages(stages []config.KanikoStage, opts *config.KanikoOptions) e
if err := saveStageAsTarball(c.From, sourceImage); err != nil {
return err
}
if err := extractImageToDependencyDir(c.From, sourceImage); err != nil {
if err := extractImageToDependencyDir(c.From, sourceImage, opts.ImageFSExtractProgress); err != nil {
return err
}
}
Expand All @@ -960,15 +960,15 @@ func fromPreviousStage(copyCommand *instructions.CopyCommand, previousStageNames
return false
}

func extractImageToDependencyDir(name string, image v1.Image) error {
func extractImageToDependencyDir(name string, image v1.Image, extractionProgress bool) error {
t := timing.Start("Extracting Image to Dependency Dir")
defer timing.DefaultRun.Stop(t)
dependencyDir := filepath.Join(config.KanikoDir, name)
if err := os.MkdirAll(dependencyDir, 0755); err != nil {
return err
}
logrus.Debugf("Trying to extract to %s", dependencyDir)
_, err := util.GetFSFromImage(dependencyDir, image, util.ExtractFile)
_, err := util.GetFSFromImage(dependencyDir, image, util.ExtractFile, extractionProgress)
return err
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/executor/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,7 @@ func Test_stageBuilder_build(t *testing.T) {
config *v1.ConfigFile
stage config.KanikoStage
crossStageDeps map[int][]string
mockGetFSFromImage func(root string, img v1.Image, extract util.ExtractFunction) ([]string, error)
mockGetFSFromImage func(root string, img v1.Image, extract util.ExtractFunction, extractionProgress bool) ([]string, error)
shouldInitSnapshot bool
}

Expand Down Expand Up @@ -1441,7 +1441,7 @@ RUN foobar
opts: &config.KanikoOptions{InitialFSUnpacked: true},
stage: config.KanikoStage{Index: 0},
crossStageDeps: map[int][]string{0: {"some-dep"}},
mockGetFSFromImage: func(root string, img v1.Image, extract util.ExtractFunction) ([]string, error) {
mockGetFSFromImage: func(root string, img v1.Image, extract util.ExtractFunction, extractionPogress bool) ([]string, error) {
return nil, fmt.Errorf("getFSFromImage shouldn't be called if fs is already unpacked")
},
},
Expand Down
76 changes: 41 additions & 35 deletions pkg/util/fs_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ type FileContext struct {
type ExtractFunction func(string, *tar.Header, string, io.Reader) error

type FSConfig struct {
includeWhiteout bool
printExtractionProgress bool
extractFunc ExtractFunction
includeWhiteout bool
extractFunc ExtractFunction
extractionProgress bool
}

type FSOpt func(*FSConfig)
Expand Down Expand Up @@ -127,21 +127,21 @@ func IncludeWhiteout() FSOpt {
}
}

func PrintExtractionProgress() FSOpt {
func ExtractFunc(extractFunc ExtractFunction) FSOpt {
return func(opts *FSConfig) {
opts.printExtractionProgress = true
opts.extractFunc = extractFunc
}
}

func ExtractFunc(extractFunc ExtractFunction) FSOpt {
func ExtractionProgress() FSOpt {
return func(opts *FSConfig) {
opts.extractFunc = extractFunc
opts.extractionProgress = true
}
}

// GetFSFromImage extracts the layers of img to root
// It returns a list of all files extracted
func GetFSFromImage(root string, img v1.Image, extract ExtractFunction) ([]string, error) {
func GetFSFromImage(root string, img v1.Image, extract ExtractFunction, extractionProgress bool) ([]string, error) {
if img == nil {
return nil, errors.New("image cannot be nil")
}
Expand All @@ -151,7 +151,11 @@ func GetFSFromImage(root string, img v1.Image, extract ExtractFunction) ([]strin
return nil, err
}

return GetFSFromLayers(root, layers, ExtractFunc(extract), PrintExtractionProgress())
opts := []FSOpt{ExtractFunc(extract)}
if extractionProgress {
opts = append(opts, ExtractionProgress())
}
return GetFSFromLayers(root, layers, opts...)
}

func GetFSFromLayers(root string, layers []v1.Layer, opts ...FSOpt) ([]string, error) {
Expand Down Expand Up @@ -180,43 +184,37 @@ func GetFSFromLayers(root string, layers []v1.Layer, opts ...FSOpt) ([]string, e
layerSizes = append(layerSizes, layerSize)
totalSize += layerSize
}
printExtractionProgress := cfg.printExtractionProgress
if totalSize == 0 {
printExtractionProgress = false
}

if printExtractionProgress {
showProgress := totalSize > 0 && cfg.extractionProgress
if showProgress {
logrus.Infof("Extracting image layers to %s", root)
}

extractedFiles := []string{}
var extractedBytes int64
for i, l := range layers {
if mediaType, err := l.MediaType(); err == nil {
logrus.Tracef("Extracting layer %d/%d of media type %s", i+1, len(layers), mediaType)
logrus.Tracef("Extracting layer %d (%d/%d) of media type %s", i, i+1, len(layers), mediaType)
} else {
logrus.Tracef("Extracting layer %d/%d", i+1, len(layers))
logrus.Tracef("Extracting layer %d (%d/%d)", i, i+1, len(layers))
}

progressPerc := float64(extractedBytes) / float64(totalSize) * 100
if printExtractionProgress {
if showProgress {
logrus.Infof("Extracting layer %d/%d (%.1f%%)", i+1, len(layers), progressPerc)
}

r, err := l.Uncompressed()
cr, err := l.Uncompressed()
if err != nil {
return nil, err
}
defer r.Close()
defer cr.Close()

if printExtractionProgress {
r = &printAfterReader{
ReadCloser: r,
after: time.Second,
print: func(n int) {
logrus.Infof("Extracting layer %d/%d (%.1f%%) %s", i+1, len(layers), progressPerc, strings.Repeat(".", n))
},
}
var r io.Reader = cr
if showProgress {
r = newDoAfterReader(r, func(count int) {
logrus.Infof("Extracting layer %d/%d (%.1f%%) %s", i+1, len(layers), progressPerc, strings.Repeat(".", count))
}, time.Second)
}

tr := tar.NewReader(r)
Expand Down Expand Up @@ -271,32 +269,40 @@ func GetFSFromLayers(root string, layers []v1.Layer, opts ...FSOpt) ([]string, e
extractedBytes += layerSizes[i]
}

if printExtractionProgress {
if showProgress {
logrus.Infof("Extraction complete")
}

return extractedFiles, nil
}

type printAfterReader struct {
io.ReadCloser
type doAfterReader struct {
r io.Reader
t time.Time
after time.Duration
count int
print func(int)
do func(int)
}

func newDoAfterReader(r io.Reader, do func(int), after time.Duration) *doAfterReader {
return &doAfterReader{
r: r,
do: do,
after: after,
}
}

func (r *printAfterReader) Read(p []byte) (n int, err error) {
n, err = r.ReadCloser.Read(p)
func (r *doAfterReader) Read(p []byte) (n int, err error) {
n, err = r.r.Read(p)
if r.t.IsZero() {
r.t = time.Now()
}
if time.Since(r.t) >= r.after {
r.count++
r.print(r.count)
r.do(r.count)
r.t = time.Now()
}
return
return n, err
}

// DeleteFilesystem deletes the extracted image file system
Expand Down
Loading