@@ -35,16 +35,43 @@ type UnpackResult struct {
35
35
VersionedHome string `json:"versioned-home" yaml:"versioned-home"`
36
36
}
37
37
38
+ type copyFunc func (dst io.Writer , src io.Reader ) (written int64 , err error )
39
+ type mkdirAllFunc func (name string , perm fs.FileMode ) error
40
+ type openFileFunc func (name string , flag int , perm fs.FileMode ) (* os.File , error )
41
+ type unarchiveFunc func (log * logger.Logger , archivePath , dataDir string , flavor string , copy copyFunc , mkdirAll mkdirAllFunc , openFile openFileFunc ) (UnpackResult , error )
42
+
43
+ type unpacker struct {
44
+ log * logger.Logger
45
+ // Abstractsions for testability
46
+ unzip unarchiveFunc
47
+ untar unarchiveFunc
48
+ // stdlib abstractions for testability
49
+ copy copyFunc
50
+ mkdirAll mkdirAllFunc
51
+ openFile openFileFunc
52
+ }
53
+
54
+ func newUnpacker (log * logger.Logger ) * unpacker {
55
+ return & unpacker {
56
+ log : log ,
57
+ unzip : unzip ,
58
+ untar : untar ,
59
+ copy : io .Copy ,
60
+ mkdirAll : os .MkdirAll ,
61
+ openFile : os .OpenFile ,
62
+ }
63
+ }
64
+
38
65
// unpack unpacks archive correctly, skips root (symlink, config...) unpacks data/*
39
- func (u * Upgrader ) unpack (version , archivePath , dataDir string , flavor string ) (UnpackResult , error ) {
66
+ func (u * unpacker ) unpack (version , archivePath , dataDir string , flavor string ) (UnpackResult , error ) {
40
67
// unpack must occur in directory that holds the installation directory
41
68
// or the extraction will be double nested
42
69
var unpackRes UnpackResult
43
70
var err error
44
71
if runtime .GOOS == windows {
45
- unpackRes , err = unzip (u .log , archivePath , dataDir , flavor )
72
+ unpackRes , err = u . unzip (u .log , archivePath , dataDir , flavor , u . copy , u . mkdirAll , u . openFile )
46
73
} else {
47
- unpackRes , err = untar (u .log , archivePath , dataDir , flavor )
74
+ unpackRes , err = u . untar (u .log , archivePath , dataDir , flavor , u . copy , u . mkdirAll , u . openFile )
48
75
}
49
76
50
77
if err != nil {
@@ -61,7 +88,7 @@ type packageMetadata struct {
61
88
hash string
62
89
}
63
90
64
- func (u * Upgrader ) getPackageMetadata (archivePath string ) (packageMetadata , error ) {
91
+ func (u * unpacker ) getPackageMetadata (archivePath string ) (packageMetadata , error ) {
65
92
ext := filepath .Ext (archivePath )
66
93
if ext == ".gz" {
67
94
// if we got gzip extension we need another extension before last
@@ -78,7 +105,8 @@ func (u *Upgrader) getPackageMetadata(archivePath string) (packageMetadata, erro
78
105
}
79
106
}
80
107
81
- func unzip (log * logger.Logger , archivePath , dataDir string , flavor string ) (UnpackResult , error ) {
108
+ // injecting copy, mkdirAll and openFile for testability
109
+ func unzip (log * logger.Logger , archivePath , dataDir string , flavor string , copy copyFunc , mkdirAll mkdirAllFunc , openFile openFileFunc ) (UnpackResult , error ) {
82
110
var hash , rootDir string
83
111
r , err := zip .OpenReader (archivePath )
84
112
if err != nil {
@@ -148,8 +176,10 @@ func unzip(log *logger.Logger, archivePath, dataDir string, flavor string) (Unpa
148
176
// check if the directory already exists
149
177
_ , err = os .Stat (dstPath )
150
178
if errors .Is (err , fs .ErrNotExist ) {
151
- // the directory does not exist, create it and any non-existing parent directory with the same permissions
152
- if err := os .MkdirAll (dstPath , f .Mode ().Perm ()& 0770 ); err != nil {
179
+ // the directory does not exist, create it and any non-existing
180
+ // parent directory with the same permissions.
181
+ // Using mkdirAll instead of os.MkdirAll so that we can mock it in tests.
182
+ if err := mkdirAll (dstPath , f .Mode ().Perm ()& 0770 ); err != nil {
153
183
return fmt .Errorf ("creating directory %q: %w" , dstPath , err )
154
184
}
155
185
} else if err != nil {
@@ -162,13 +192,23 @@ func unzip(log *logger.Logger, archivePath, dataDir string, flavor string) (Unpa
162
192
}
163
193
}
164
194
165
- _ = os .MkdirAll (dstPath , f .Mode ()& 0770 )
195
+ // Using mkdirAll instead of os.MkdirAll so that we can mock it in tests.
196
+ err = mkdirAll (dstPath , f .Mode ()& 0770 )
197
+ if err != nil {
198
+ return fmt .Errorf ("creating directory %q: %w" , dstPath , err )
199
+ }
166
200
} else {
167
201
log .Debugw ("Unpacking file" , "archive" , "zip" , "file.path" , dstPath )
168
202
// create non-existing containing folders with 0770 permissions right now, we'll fix the permission of each
169
- // directory as we come across them while processing the other package entries
170
- _ = os .MkdirAll (filepath .Dir (dstPath ), 0770 )
171
- f , err := os .OpenFile (dstPath , os .O_WRONLY | os .O_CREATE | os .O_TRUNC , f .Mode ()& 0770 )
203
+ // directory as we come across them while processing the other
204
+ // package entries
205
+ // Using mkdirAll instead of os.MkdirAll so that we can mock it in tests.
206
+ err = mkdirAll (filepath .Dir (dstPath ), 0770 )
207
+ if err != nil {
208
+ return fmt .Errorf ("creating directory %q: %w" , dstPath , err )
209
+ }
210
+ // Using openFile instead of os.OpenFile so that we can mock it in tests.
211
+ f , err := openFile (dstPath , os .O_WRONLY | os .O_CREATE | os .O_TRUNC , f .Mode ()& 0770 )
172
212
if err != nil {
173
213
return err
174
214
}
@@ -178,7 +218,9 @@ func unzip(log *logger.Logger, archivePath, dataDir string, flavor string) (Unpa
178
218
}
179
219
}()
180
220
181
- if _ , err = io .Copy (f , rc ); err != nil { //nolint:gosec // legacy
221
+ // Using copy instead of io.Copy so that we can
222
+ // mock it in tests.
223
+ if _ , err = copy (f , rc ); err != nil {
182
224
return err
183
225
}
184
226
}
@@ -313,7 +355,8 @@ func getPackageMetadataFromZipReader(r *zip.ReadCloser, fileNamePrefix string) (
313
355
return ret , nil
314
356
}
315
357
316
- func untar (log * logger.Logger , archivePath , dataDir string , flavor string ) (UnpackResult , error ) {
358
+ // injecting copy, mkdirAll and openFile for testability
359
+ func untar (log * logger.Logger , archivePath , dataDir string , flavor string , copy copyFunc , mkdirAll mkdirAllFunc , openFile openFileFunc ) (UnpackResult , error ) {
317
360
var versionedHome string
318
361
var rootDir string
319
362
var hash string
@@ -413,17 +456,23 @@ func untar(log *logger.Logger, archivePath, dataDir string, flavor string) (Unpa
413
456
log .Debugw ("Unpacking file" , "archive" , "tar" , "file.path" , abs )
414
457
// create non-existing containing folders with 0750 permissions right now, we'll fix the permission of each
415
458
// directory as we come across them while processing the other package entries
416
- if err = os .MkdirAll (filepath .Dir (abs ), 0750 ); err != nil {
417
- return UnpackResult {}, errors .New (err , "TarInstaller: creating directory for file " + abs , errors .TypeFilesystem , errors .M (errors .MetaKeyPath , abs ))
459
+ // Using mkdirAll instead of os.MkdirAll so that we can
460
+ // mock it in tests.
461
+ if err = mkdirAll (filepath .Dir (abs ), 0750 ); err != nil {
462
+ return UnpackResult {}, goerrors .Join (err , errors .New ("TarInstaller: creating directory for file " + abs , errors .TypeFilesystem , errors .M (errors .MetaKeyPath , abs )))
418
463
}
419
464
420
465
// remove any world permissions from the file
421
- wf , err := os .OpenFile (abs , os .O_RDWR | os .O_CREATE | os .O_TRUNC , mode .Perm ()& 0770 )
466
+ // Using openFile instead of os.OpenFile so that we can
467
+ // mock it in tests.
468
+ wf , err := openFile (abs , os .O_RDWR | os .O_CREATE | os .O_TRUNC , mode .Perm ()& 0770 )
422
469
if err != nil {
423
- return UnpackResult {}, errors . New (err , "TarInstaller: creating file " + abs , errors .TypeFilesystem , errors .M (errors .MetaKeyPath , abs ))
470
+ return UnpackResult {}, goerrors . Join (err , errors . New ( "TarInstaller: creating file " + abs , errors .TypeFilesystem , errors .M (errors .MetaKeyPath , abs ) ))
424
471
}
425
472
426
- _ , err = io .Copy (wf , tr ) //nolint:gosec // legacy
473
+ // Using copy instead of io.Copy so that we can
474
+ // mock it in tests.
475
+ _ , err = copy (wf , tr )
427
476
if closeErr := wf .Close (); closeErr != nil && err == nil {
428
477
err = closeErr
429
478
}
@@ -435,17 +484,20 @@ func untar(log *logger.Logger, archivePath, dataDir string, flavor string) (Unpa
435
484
// check if the directory already exists
436
485
_ , err = os .Stat (abs )
437
486
if errors .Is (err , fs .ErrNotExist ) {
438
- // the directory does not exist, create it and any non-existing parent directory with the same permissions
439
- if err := os .MkdirAll (abs , mode .Perm ()& 0770 ); err != nil {
440
- return UnpackResult {}, errors .New (err , "TarInstaller: creating directory for file " + abs , errors .TypeFilesystem , errors .M (errors .MetaKeyPath , abs ))
487
+ // the directory does not exist, create it and any non-existing
488
+ // parent directory with the same permissions.
489
+ // Using mkdirAll instead of os.MkdirAll so that we can
490
+ // mock it in tests.
491
+ if err := mkdirAll (abs , mode .Perm ()& 0770 ); err != nil {
492
+ return UnpackResult {}, goerrors .Join (err , errors .New ("TarInstaller: creating directory for file " + abs , errors .TypeFilesystem , errors .M (errors .MetaKeyPath , abs )))
441
493
}
442
494
} else if err != nil {
443
495
return UnpackResult {}, errors .New (err , "TarInstaller: stat() directory for file " + abs , errors .TypeFilesystem , errors .M (errors .MetaKeyPath , abs ))
444
496
} else {
445
497
// directory already exists, set the appropriate permissions
446
498
err = os .Chmod (abs , mode .Perm ()& 0770 )
447
499
if err != nil {
448
- 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 ))
500
+ 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 ) ))
449
501
}
450
502
}
451
503
default :
0 commit comments