Skip to content

Commit 29565d7

Browse files
kaanyaltimergify[bot]
authored andcommitted
Enhancement/5235 handle insufficient disk space errors in artifact unpack (#9322)
* enhancement(5235): added stdlib wrappers in step_unpack * enhancement(5235): wrap errors in step_unpack * enhancement(5235): removed nolint:gosec comment from step_unpack * enhancement(5235): refactored unpack step to abstract the unpack function from upgrader. abstracted unzip and untar to be able to test unpack function. updated unpack step tests to test for stdlib failures in unzip and untar and added tests for unpack * enhancement(5235}: added unpackHandler interface to abstract the unpack function from upgrader * enhancement(5235): added getPackageMetadata into unpackHandler interface for testability * enhancement(5235): using unpacker.getPackageMetadata * enhancement(5235): added abstractions in upgrader for testability * enhancement(5235): removed unpack specific test from upgrade tests, added test cases in the upgrade error handling test * enhancement(5235): using formatted test assertion api (cherry picked from commit f70ff02) # Conflicts: # internal/pkg/agent/application/upgrade/step_unpack.go # internal/pkg/agent/application/upgrade/step_unpack_test.go # internal/pkg/agent/application/upgrade/upgrade.go
1 parent ade8451 commit 29565d7

File tree

4 files changed

+491
-33
lines changed

4 files changed

+491
-33
lines changed

internal/pkg/agent/application/upgrade/step_unpack.go

Lines changed: 87 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,53 @@ type UnpackResult struct {
3333
VersionedHome string `json:"versioned-home" yaml:"versioned-home"`
3434
}
3535

36+
type copyFunc func(dst io.Writer, src io.Reader) (written int64, err error)
37+
type mkdirAllFunc func(name string, perm fs.FileMode) error
38+
type openFileFunc func(name string, flag int, perm fs.FileMode) (*os.File, error)
39+
type unarchiveFunc func(log *logger.Logger, archivePath, dataDir string, flavor string, copy copyFunc, mkdirAll mkdirAllFunc, openFile openFileFunc) (UnpackResult, error)
40+
41+
type unpacker struct {
42+
log *logger.Logger
43+
// Abstractsions for testability
44+
unzip unarchiveFunc
45+
untar unarchiveFunc
46+
// stdlib abstractions for testability
47+
copy copyFunc
48+
mkdirAll mkdirAllFunc
49+
openFile openFileFunc
50+
}
51+
52+
func newUnpacker(log *logger.Logger) *unpacker {
53+
return &unpacker{
54+
log: log,
55+
unzip: unzip,
56+
untar: untar,
57+
copy: io.Copy,
58+
mkdirAll: os.MkdirAll,
59+
openFile: os.OpenFile,
60+
}
61+
}
62+
3663
// unpack unpacks archive correctly, skips root (symlink, config...) unpacks data/*
64+
<<<<<<< HEAD
3765
func (u *Upgrader) unpack(version, archivePath, dataDir string) (UnpackResult, error) {
66+
=======
67+
func (u *unpacker) unpack(version, archivePath, dataDir string, flavor string) (UnpackResult, error) {
68+
>>>>>>> f70ff023f (Enhancement/5235 handle insufficient disk space errors in artifact unpack (#9322))
3869
// unpack must occur in directory that holds the installation directory
3970
// or the extraction will be double nested
4071
var unpackRes UnpackResult
4172
var err error
4273
if runtime.GOOS == windows {
74+
<<<<<<< HEAD
4375
unpackRes, err = unzip(u.log, archivePath, dataDir)
4476
} else {
4577
unpackRes, err = untar(u.log, archivePath, dataDir)
78+
=======
79+
unpackRes, err = u.unzip(u.log, archivePath, dataDir, flavor, u.copy, u.mkdirAll, u.openFile)
80+
} else {
81+
unpackRes, err = u.untar(u.log, archivePath, dataDir, flavor, u.copy, u.mkdirAll, u.openFile)
82+
>>>>>>> f70ff023f (Enhancement/5235 handle insufficient disk space errors in artifact unpack (#9322))
4683
}
4784

4885
if err != nil {
@@ -59,7 +96,7 @@ type packageMetadata struct {
5996
hash string
6097
}
6198

62-
func (u *Upgrader) getPackageMetadata(archivePath string) (packageMetadata, error) {
99+
func (u *unpacker) getPackageMetadata(archivePath string) (packageMetadata, error) {
63100
ext := filepath.Ext(archivePath)
64101
if ext == ".gz" {
65102
// if we got gzip extension we need another extension before last
@@ -76,7 +113,12 @@ func (u *Upgrader) getPackageMetadata(archivePath string) (packageMetadata, erro
76113
}
77114
}
78115

116+
<<<<<<< HEAD
79117
func unzip(log *logger.Logger, archivePath, dataDir string) (UnpackResult, error) {
118+
=======
119+
// injecting copy, mkdirAll and openFile for testability
120+
func unzip(log *logger.Logger, archivePath, dataDir string, flavor string, copy copyFunc, mkdirAll mkdirAllFunc, openFile openFileFunc) (UnpackResult, error) {
121+
>>>>>>> f70ff023f (Enhancement/5235 handle insufficient disk space errors in artifact unpack (#9322))
80122
var hash, rootDir string
81123
r, err := zip.OpenReader(archivePath)
82124
if err != nil {
@@ -136,8 +178,10 @@ func unzip(log *logger.Logger, archivePath, dataDir string) (UnpackResult, error
136178
// check if the directory already exists
137179
_, err = os.Stat(dstPath)
138180
if errors.Is(err, fs.ErrNotExist) {
139-
// the directory does not exist, create it and any non-existing parent directory with the same permissions
140-
if err := os.MkdirAll(dstPath, f.Mode().Perm()&0770); err != nil {
181+
// the directory does not exist, create it and any non-existing
182+
// parent directory with the same permissions.
183+
// Using mkdirAll instead of os.MkdirAll so that we can mock it in tests.
184+
if err := mkdirAll(dstPath, f.Mode().Perm()&0770); err != nil {
141185
return fmt.Errorf("creating directory %q: %w", dstPath, err)
142186
}
143187
} else if err != nil {
@@ -150,13 +194,23 @@ func unzip(log *logger.Logger, archivePath, dataDir string) (UnpackResult, error
150194
}
151195
}
152196

153-
_ = os.MkdirAll(dstPath, f.Mode()&0770)
197+
// Using mkdirAll instead of os.MkdirAll so that we can mock it in tests.
198+
err = mkdirAll(dstPath, f.Mode()&0770)
199+
if err != nil {
200+
return fmt.Errorf("creating directory %q: %w", dstPath, err)
201+
}
154202
} else {
155203
log.Debugw("Unpacking file", "archive", "zip", "file.path", dstPath)
156204
// create non-existing containing folders with 0770 permissions right now, we'll fix the permission of each
157-
// directory as we come across them while processing the other package entries
158-
_ = os.MkdirAll(filepath.Dir(dstPath), 0770)
159-
f, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()&0770)
205+
// directory as we come across them while processing the other
206+
// package entries
207+
// Using mkdirAll instead of os.MkdirAll so that we can mock it in tests.
208+
err = mkdirAll(filepath.Dir(dstPath), 0770)
209+
if err != nil {
210+
return fmt.Errorf("creating directory %q: %w", dstPath, err)
211+
}
212+
// Using openFile instead of os.OpenFile so that we can mock it in tests.
213+
f, err := openFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()&0770)
160214
if err != nil {
161215
return err
162216
}
@@ -166,7 +220,9 @@ func unzip(log *logger.Logger, archivePath, dataDir string) (UnpackResult, error
166220
}
167221
}()
168222

169-
if _, err = io.Copy(f, rc); err != nil { //nolint:gosec // legacy
223+
// Using copy instead of io.Copy so that we can
224+
// mock it in tests.
225+
if _, err = copy(f, rc); err != nil {
170226
return err
171227
}
172228
}
@@ -240,8 +296,13 @@ func getPackageMetadataFromZipReader(r *zip.ReadCloser, fileNamePrefix string) (
240296
return ret, nil
241297
}
242298

299+
<<<<<<< HEAD
243300
func untar(log *logger.Logger, archivePath, dataDir string) (UnpackResult, error) {
244301

302+
=======
303+
// injecting copy, mkdirAll and openFile for testability
304+
func untar(log *logger.Logger, archivePath, dataDir string, flavor string, copy copyFunc, mkdirAll mkdirAllFunc, openFile openFileFunc) (UnpackResult, error) {
305+
>>>>>>> f70ff023f (Enhancement/5235 handle insufficient disk space errors in artifact unpack (#9322))
245306
var versionedHome string
246307
var rootDir string
247308
var hash string
@@ -330,17 +391,23 @@ func untar(log *logger.Logger, archivePath, dataDir string) (UnpackResult, error
330391
log.Debugw("Unpacking file", "archive", "tar", "file.path", abs)
331392
// create non-existing containing folders with 0750 permissions right now, we'll fix the permission of each
332393
// directory as we come across them while processing the other package entries
333-
if err = os.MkdirAll(filepath.Dir(abs), 0750); err != nil {
334-
return UnpackResult{}, errors.New(err, "TarInstaller: creating directory for file "+abs, errors.TypeFilesystem, errors.M(errors.MetaKeyPath, abs))
394+
// Using mkdirAll instead of os.MkdirAll so that we can
395+
// mock it in tests.
396+
if err = mkdirAll(filepath.Dir(abs), 0750); err != nil {
397+
return UnpackResult{}, goerrors.Join(err, errors.New("TarInstaller: creating directory for file "+abs, errors.TypeFilesystem, errors.M(errors.MetaKeyPath, abs)))
335398
}
336399

337400
// remove any world permissions from the file
338-
wf, err := os.OpenFile(abs, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode.Perm()&0770)
401+
// Using openFile instead of os.OpenFile so that we can
402+
// mock it in tests.
403+
wf, err := openFile(abs, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode.Perm()&0770)
339404
if err != nil {
340-
return UnpackResult{}, errors.New(err, "TarInstaller: creating file "+abs, errors.TypeFilesystem, errors.M(errors.MetaKeyPath, abs))
405+
return UnpackResult{}, goerrors.Join(err, errors.New("TarInstaller: creating file "+abs, errors.TypeFilesystem, errors.M(errors.MetaKeyPath, abs)))
341406
}
342407

343-
_, err = io.Copy(wf, tr) //nolint:gosec // legacy
408+
// Using copy instead of io.Copy so that we can
409+
// mock it in tests.
410+
_, err = copy(wf, tr)
344411
if closeErr := wf.Close(); closeErr != nil && err == nil {
345412
err = closeErr
346413
}
@@ -352,17 +419,20 @@ func untar(log *logger.Logger, archivePath, dataDir string) (UnpackResult, error
352419
// check if the directory already exists
353420
_, err = os.Stat(abs)
354421
if errors.Is(err, fs.ErrNotExist) {
355-
// the directory does not exist, create it and any non-existing parent directory with the same permissions
356-
if err := os.MkdirAll(abs, mode.Perm()&0770); err != nil {
357-
return UnpackResult{}, errors.New(err, "TarInstaller: creating directory for file "+abs, errors.TypeFilesystem, errors.M(errors.MetaKeyPath, abs))
422+
// the directory does not exist, create it and any non-existing
423+
// parent directory with the same permissions.
424+
// Using mkdirAll instead of os.MkdirAll so that we can
425+
// mock it in tests.
426+
if err := mkdirAll(abs, mode.Perm()&0770); err != nil {
427+
return UnpackResult{}, goerrors.Join(err, errors.New("TarInstaller: creating directory for file "+abs, errors.TypeFilesystem, errors.M(errors.MetaKeyPath, abs)))
358428
}
359429
} else if err != nil {
360430
return UnpackResult{}, errors.New(err, "TarInstaller: stat() directory for file "+abs, errors.TypeFilesystem, errors.M(errors.MetaKeyPath, abs))
361431
} else {
362432
// directory already exists, set the appropriate permissions
363433
err = os.Chmod(abs, mode.Perm()&0770)
364434
if err != nil {
365-
return UnpackResult{}, errors.New(err, fmt.Sprintf("TarInstaller: setting permissions %O for directory %q", mode.Perm()&0770, abs), errors.TypeFilesystem, errors.M(errors.MetaKeyPath, abs))
435+
return UnpackResult{}, goerrors.Join(err, errors.New("TarInstaller: setting permissions %O for directory %q", mode.Perm()&0770, abs, errors.TypeFilesystem, errors.M(errors.MetaKeyPath, abs)))
366436
}
367437
}
368438
default:

0 commit comments

Comments
 (0)