@@ -33,16 +33,43 @@ type UnpackResult struct {
33
33
VersionedHome string `json:"versioned-home" yaml:"versioned-home"`
34
34
}
35
35
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 , 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
+
36
63
// unpack unpacks archive correctly, skips root (symlink, config...) unpacks data/*
37
- func (u * Upgrader ) unpack (version , archivePath , dataDir string ) (UnpackResult , error ) {
64
+ func (u * unpacker ) unpack (version , archivePath , dataDir string ) (UnpackResult , error ) {
38
65
// unpack must occur in directory that holds the installation directory
39
66
// or the extraction will be double nested
40
67
var unpackRes UnpackResult
41
68
var err error
42
69
if runtime .GOOS == windows {
43
- unpackRes , err = unzip (u .log , archivePath , dataDir )
70
+ unpackRes , err = u . unzip (u .log , archivePath , dataDir , u . copy , u . mkdirAll , u . openFile )
44
71
} else {
45
- unpackRes , err = untar (u .log , archivePath , dataDir )
72
+ unpackRes , err = u . untar (u .log , archivePath , dataDir , u . copy , u . mkdirAll , u . openFile )
46
73
}
47
74
48
75
if err != nil {
@@ -59,7 +86,7 @@ type packageMetadata struct {
59
86
hash string
60
87
}
61
88
62
- func (u * Upgrader ) getPackageMetadata (archivePath string ) (packageMetadata , error ) {
89
+ func (u * unpacker ) getPackageMetadata (archivePath string ) (packageMetadata , error ) {
63
90
ext := filepath .Ext (archivePath )
64
91
if ext == ".gz" {
65
92
// if we got gzip extension we need another extension before last
@@ -76,7 +103,8 @@ func (u *Upgrader) getPackageMetadata(archivePath string) (packageMetadata, erro
76
103
}
77
104
}
78
105
79
- func unzip (log * logger.Logger , archivePath , dataDir string ) (UnpackResult , error ) {
106
+ // injecting copy, mkdirAll and openFile for testability
107
+ func unzip (log * logger.Logger , archivePath , dataDir string , copy copyFunc , mkdirAll mkdirAllFunc , openFile openFileFunc ) (UnpackResult , error ) {
80
108
var hash , rootDir string
81
109
r , err := zip .OpenReader (archivePath )
82
110
if err != nil {
@@ -136,8 +164,10 @@ func unzip(log *logger.Logger, archivePath, dataDir string) (UnpackResult, error
136
164
// check if the directory already exists
137
165
_ , err = os .Stat (dstPath )
138
166
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 {
167
+ // the directory does not exist, create it and any non-existing
168
+ // parent directory with the same permissions.
169
+ // Using mkdirAll instead of os.MkdirAll so that we can mock it in tests.
170
+ if err := mkdirAll (dstPath , f .Mode ().Perm ()& 0770 ); err != nil {
141
171
return fmt .Errorf ("creating directory %q: %w" , dstPath , err )
142
172
}
143
173
} else if err != nil {
@@ -150,13 +180,23 @@ func unzip(log *logger.Logger, archivePath, dataDir string) (UnpackResult, error
150
180
}
151
181
}
152
182
153
- _ = os .MkdirAll (dstPath , f .Mode ()& 0770 )
183
+ // Using mkdirAll instead of os.MkdirAll so that we can mock it in tests.
184
+ err = mkdirAll (dstPath , f .Mode ()& 0770 )
185
+ if err != nil {
186
+ return fmt .Errorf ("creating directory %q: %w" , dstPath , err )
187
+ }
154
188
} else {
155
189
log .Debugw ("Unpacking file" , "archive" , "zip" , "file.path" , dstPath )
156
190
// 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 )
191
+ // directory as we come across them while processing the other
192
+ // package entries
193
+ // Using mkdirAll instead of os.MkdirAll so that we can mock it in tests.
194
+ err = mkdirAll (filepath .Dir (dstPath ), 0770 )
195
+ if err != nil {
196
+ return fmt .Errorf ("creating directory %q: %w" , dstPath , err )
197
+ }
198
+ // Using openFile instead of os.OpenFile so that we can mock it in tests.
199
+ f , err := openFile (dstPath , os .O_WRONLY | os .O_CREATE | os .O_TRUNC , f .Mode ()& 0770 )
160
200
if err != nil {
161
201
return err
162
202
}
@@ -166,7 +206,9 @@ func unzip(log *logger.Logger, archivePath, dataDir string) (UnpackResult, error
166
206
}
167
207
}()
168
208
169
- if _ , err = io .Copy (f , rc ); err != nil { //nolint:gosec // legacy
209
+ // Using copy instead of io.Copy so that we can
210
+ // mock it in tests.
211
+ if _ , err = copy (f , rc ); err != nil {
170
212
return err
171
213
}
172
214
}
@@ -240,8 +282,8 @@ func getPackageMetadataFromZipReader(r *zip.ReadCloser, fileNamePrefix string) (
240
282
return ret , nil
241
283
}
242
284
243
- func untar ( log * logger. Logger , archivePath , dataDir string ) ( UnpackResult , error ) {
244
-
285
+ // injecting copy, mkdirAll and openFile for testability
286
+ func untar ( log * logger. Logger , archivePath , dataDir string , copy copyFunc , mkdirAll mkdirAllFunc , openFile openFileFunc ) ( UnpackResult , error ) {
245
287
var versionedHome string
246
288
var rootDir string
247
289
var hash string
@@ -330,17 +372,23 @@ func untar(log *logger.Logger, archivePath, dataDir string) (UnpackResult, error
330
372
log .Debugw ("Unpacking file" , "archive" , "tar" , "file.path" , abs )
331
373
// create non-existing containing folders with 0750 permissions right now, we'll fix the permission of each
332
374
// 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 ))
375
+ // Using mkdirAll instead of os.MkdirAll so that we can
376
+ // mock it in tests.
377
+ if err = mkdirAll (filepath .Dir (abs ), 0750 ); err != nil {
378
+ return UnpackResult {}, goerrors .Join (err , errors .New ("TarInstaller: creating directory for file " + abs , errors .TypeFilesystem , errors .M (errors .MetaKeyPath , abs )))
335
379
}
336
380
337
381
// 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 )
382
+ // Using openFile instead of os.OpenFile so that we can
383
+ // mock it in tests.
384
+ wf , err := openFile (abs , os .O_RDWR | os .O_CREATE | os .O_TRUNC , mode .Perm ()& 0770 )
339
385
if err != nil {
340
- return UnpackResult {}, errors . New (err , "TarInstaller: creating file " + abs , errors .TypeFilesystem , errors .M (errors .MetaKeyPath , abs ))
386
+ return UnpackResult {}, goerrors . Join (err , errors . New ( "TarInstaller: creating file " + abs , errors .TypeFilesystem , errors .M (errors .MetaKeyPath , abs ) ))
341
387
}
342
388
343
- _ , err = io .Copy (wf , tr ) //nolint:gosec // legacy
389
+ // Using copy instead of io.Copy so that we can
390
+ // mock it in tests.
391
+ _ , err = copy (wf , tr )
344
392
if closeErr := wf .Close (); closeErr != nil && err == nil {
345
393
err = closeErr
346
394
}
@@ -352,17 +400,20 @@ func untar(log *logger.Logger, archivePath, dataDir string) (UnpackResult, error
352
400
// check if the directory already exists
353
401
_ , err = os .Stat (abs )
354
402
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 ))
403
+ // the directory does not exist, create it and any non-existing
404
+ // parent directory with the same permissions.
405
+ // Using mkdirAll instead of os.MkdirAll so that we can
406
+ // mock it in tests.
407
+ if err := mkdirAll (abs , mode .Perm ()& 0770 ); err != nil {
408
+ return UnpackResult {}, goerrors .Join (err , errors .New ("TarInstaller: creating directory for file " + abs , errors .TypeFilesystem , errors .M (errors .MetaKeyPath , abs )))
358
409
}
359
410
} else if err != nil {
360
411
return UnpackResult {}, errors .New (err , "TarInstaller: stat() directory for file " + abs , errors .TypeFilesystem , errors .M (errors .MetaKeyPath , abs ))
361
412
} else {
362
413
// directory already exists, set the appropriate permissions
363
414
err = os .Chmod (abs , mode .Perm ()& 0770 )
364
415
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 ))
416
+ 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 ) ))
366
417
}
367
418
}
368
419
default :
0 commit comments