• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SAP / cloud-mta-build-tool / 5932

17 Oct 2023 04:05AM UTC coverage: 90.237% (-6.4%) from 96.682%
5932

push

circle-ci

web-flow
MBT Support SBom Generation (#1046)

* modified:   cmd/cmd.go
	modified:   cmd/cmd_test.go
	modified:   cmd/init.go
	modified:   cmd/init_test.go
	new file:   cmd/sbom.go
	new file:   cmd/sbom_test.go
	modified:   configs/builder_type_cfg.yaml
	modified:   internal/archive/fsops.go
	modified:   internal/archive/mta_location.go
	modified:   internal/artifacts/artifacts_msg.go
	modified:   internal/artifacts/project.go
	modified:   internal/artifacts/project_test.go
	new file:   internal/artifacts/sbom.go
	new file:   internal/artifacts/sbom_test.go
	modified:   internal/commands/commands.go
	modified:   internal/commands/commands_msg.go
	modified:   internal/platform/model.go
	modified:   internal/tpl/base_args.txt
	modified:   internal/tpl/base_post.txt

* modified:   internal/artifacts/sbom_test.go
	modified:   internal/commands/builder_type_cfg.go
	modified:   internal/tpl/base_args.go
	modified:   internal/tpl/base_post.go

* modified:   internal/artifacts/sbom_test.go

* modified:   cmd/cmd.go
	modified:   cmd/init_test.go
	modified:   cmd/sbom.go
	modified:   cmd/sbom_test.go
	modified:   internal/artifacts/project.go
	modified:   internal/artifacts/sbom.go
	modified:   internal/artifacts/sbom_test.go
	modified:   internal/tpl/base_post.go
	modified:   internal/tpl/base_post.txt

* modified:   cmd/init_test.go
	modified:   cmd/sbom_test.go
	modified:   internal/artifacts/sbom.go
	modified:   internal/artifacts/sbom_test.go

* modified:   cmd/init.go
	modified:   cmd/init_test.go
	modified:   cmd/sbom.go
	modified:   cmd/sbom_test.go
	modified:   internal/artifacts/sbom.go
	modified:   internal/artifacts/sbom_test.go
	modified:   internal/commands/commands.go

* modified:   internal/artifacts/artifacts_msg.go
	modified:   internal/artifacts/sbom.go
	modified:   internal/commands/commands.go

* new file:   cmd/testdata/mta-sbom/golang/go.mod
	new file:   cmd/testdata/mta-sbom/golang/go.sum
	ne... (continued)

421 of 421 new or added lines in 8 files covered. (100.0%)

2976 of 3298 relevant lines covered (90.24%)

1.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.04
/internal/archive/fsops.go
1
package dir
2

3
import (
4
        "archive/zip"
5
        "fmt"
6
        "io"
7
        "io/ioutil"
8
        "os"
9
        "path/filepath"
10
        "sort"
11
        "strings"
12

13
        "github.com/pkg/errors"
14

15
        "github.com/SAP/cloud-mta-build-tool/internal/logs"
16
)
17

18
type fileInfoProviderI interface {
19
        isSymbolicLink(file os.FileInfo) bool
20
        isDir(file os.FileInfo) bool
21
        readlink(path string) (string, error)
22
        stat(name string) (os.FileInfo, error)
23
}
24

25
type standardFileInfoProvider struct {
26
}
27

28
func (provider *standardFileInfoProvider) isSymbolicLink(file os.FileInfo) bool {
1✔
29
        return file.Mode()&os.ModeSymlink != 0
1✔
30
}
1✔
31

32
func (provider *standardFileInfoProvider) isDir(file os.FileInfo) bool {
1✔
33
        return file.IsDir()
1✔
34
}
1✔
35

36
func (provider *standardFileInfoProvider) readlink(path string) (string, error) {
1✔
37
        return os.Readlink(path)
1✔
38
}
1✔
39

40
func (provider *standardFileInfoProvider) stat(name string) (os.FileInfo, error) {
1✔
41
        return os.Stat(name)
1✔
42
}
1✔
43

44
var fileInfoProvider fileInfoProviderI = &standardFileInfoProvider{}
45

46
// CreateDirIfNotExist - Create new dir
47
func CreateDirIfNotExist(dir string) error {
1✔
48
        info, err := os.Stat(dir)
1✔
49
        if os.IsNotExist(err) {
2✔
50
                err = os.MkdirAll(dir, os.ModePerm)
1✔
51
        } else if err == nil && !info.IsDir() {
3✔
52
                err = errors.Errorf(FolderCreationFailedMsg, dir)
1✔
53
        }
1✔
54
        return err
1✔
55
}
56

57
// RemoveDirIfExist - remove dir
58
func RemoveIfExist(dir string) error {
×
59
        _, err := os.Stat(dir)
×
60
        if os.IsNotExist(err) {
×
61
                return nil
×
62
        }
×
63
        err = os.RemoveAll(dir)
×
64
        return err
×
65
}
66

67
// Archive module and mtar artifacts,
68
// compatible with the JAR specification
69
// to support the spec requirements
70
// Source Path to be zipped
71
// Target artifact
72
func Archive(sourcePath, targetArchivePath string, ignore []string) (e error) {
1✔
73

1✔
74
        // check that folder to be packed exist
1✔
75
        info, err := fileInfoProvider.stat(sourcePath)
1✔
76
        if err != nil {
2✔
77
                return err
1✔
78
        }
1✔
79

80
        // create folder of archive file if not exists
81
        err = CreateDirIfNotExist(filepath.Dir(targetArchivePath))
1✔
82
        if err != nil {
1✔
83
                return errors.Wrapf(err, archivingFailedOnCreateFolderMsg, filepath.Dir(targetArchivePath))
×
84
        }
×
85

86
        // create archive file
87
        zipfile, err := os.Create(targetArchivePath)
1✔
88
        if err != nil {
2✔
89
                return err
1✔
90
        }
1✔
91
        defer func() {
2✔
92
                e = CloseFile(zipfile, e)
1✔
93
        }()
1✔
94

95
        // create archive writer
96
        archive := zip.NewWriter(zipfile)
1✔
97
        defer func() {
2✔
98
                e = CloseFile(archive, e)
1✔
99
        }()
1✔
100

101
        baseDir, err := getBaseDir(sourcePath, info)
1✔
102
        if err != nil {
2✔
103
                return err
1✔
104
        }
1✔
105

106
        if !strings.HasSuffix(baseDir, string(os.PathSeparator)) {
2✔
107
                baseDir += string(os.PathSeparator)
1✔
108
        }
1✔
109

110
        ignoreMap, err := getIgnoredEntries(ignore, sourcePath)
1✔
111
        if err != nil {
1✔
112
                return err
×
113
        }
×
114

115
        err = walk(sourcePath, baseDir, "", "", archive, make(map[string]bool), ignoreMap)
1✔
116
        return err
1✔
117
}
118

119
func getBaseDir(path string, info os.FileInfo) (string, error) {
1✔
120
        var err error
1✔
121
        regularInfo := info
1✔
122
        if fileInfoProvider.isSymbolicLink(info) {
2✔
123
                _, regularInfo, _, err = dereferenceSymlink(path, make(map[string]bool))
1✔
124
                if err != nil {
2✔
125
                        return "", err
1✔
126
                }
1✔
127
        }
128

129
        // Skip headers to support jar archive structure
130
        if regularInfo.IsDir() {
2✔
131
                return path, nil
1✔
132
        }
1✔
133
        return filepath.Dir(path), nil
1✔
134
}
135

136
// getIgnoresMap - getIgnores Helper
137
func getIgnoredEntries(ignore []string, sourcePath string) (map[string]interface{}, error) {
1✔
138

1✔
139
        info, err := fileInfoProvider.stat(sourcePath)
1✔
140
        if err != nil {
2✔
141
                return nil, err
1✔
142
        }
1✔
143
        regularSourcePath := sourcePath
1✔
144
        if fileInfoProvider.isSymbolicLink(info) {
2✔
145
                regularSourcePath, _, _, err = dereferenceSymlink(sourcePath, make(map[string]bool))
1✔
146
                if err != nil {
2✔
147
                        return nil, err
1✔
148
                }
1✔
149
        }
150

151
        ignoredEntriesMap := map[string]interface{}{}
1✔
152
        for _, ign := range ignore {
2✔
153
                path := filepath.Join(regularSourcePath, ign)
1✔
154
                entries, err := filepath.Glob(path)
1✔
155
                if err != nil {
1✔
156
                        return nil, err
×
157
                }
×
158

159
                for _, entry := range entries {
2✔
160
                        ignoredEntriesMap[entry] = nil
1✔
161
                }
1✔
162
        }
163
        return ignoredEntriesMap, nil
1✔
164
}
165

166
// CloseFile - closes file
167
// error handling takes into account error of the calling function
168
func CloseFile(file io.Closer, err error) error {
1✔
169
        errClose := file.Close()
1✔
170
        if errClose != nil && err == nil {
2✔
171
                return errClose
1✔
172
        }
1✔
173
        return err
1✔
174
}
175

176
func walk(sourcePath string, baseDir, symLinkPathInZip, linkedPath string, archive *zip.Writer,
177
        symlinks map[string]bool,
178
        ignore map[string]interface{}) error {
1✔
179

1✔
180
        // pack files of source into archive
1✔
181
        return filepath.Walk(sourcePath, func(path string, info os.FileInfo, err error) error {
2✔
182
                if err != nil {
1✔
183
                        return err
×
184
                }
×
185

186
                if _, ok := ignore[path]; ok {
2✔
187
                        if info.IsDir() {
2✔
188
                                return filepath.SkipDir
1✔
189
                        }
1✔
190
                        return nil
1✔
191
                }
192

193
                if fileInfoProvider.isSymbolicLink(info) {
2✔
194
                        return addSymbolicLinkToArchive(path, baseDir, symLinkPathInZip, linkedPath, archive, symlinks, ignore)
1✔
195
                }
1✔
196

197
                // Don't add the base folder to the zip
198
                if info.IsDir() && filepath.Clean(path) == filepath.Clean(baseDir) {
2✔
199
                        return nil
1✔
200
                }
1✔
201

202
                pathInZip := getPathInZip(path, baseDir, symLinkPathInZip, linkedPath, info)
1✔
203

1✔
204
                return addToArchive(path, pathInZip, info, archive)
1✔
205
        })
206
}
207

208
func getPathInZip(path string, baseDir, symLinkPath, linkedPath string, info os.FileInfo) string {
1✔
209
        if filepath.Clean(path) == filepath.Clean(baseDir) {
2✔
210
                return ""
1✔
211
        }
1✔
212
        var pathInZip string
1✔
213

1✔
214
        if linkedPath != "" {
2✔
215
                relPath := getRelativePath(path, linkedPath)
1✔
216
                pathInZip = filepath.Join(symLinkPath, relPath)
1✔
217
        } else {
2✔
218
                pathInZip = getRelativePath(path, baseDir)
1✔
219
        }
1✔
220

221
        // Path in zip should be with slashes (in all operating systems)
222
        pathInZip = filepath.ToSlash(pathInZip)
1✔
223

1✔
224
        // Folders must end with "/"
1✔
225
        if info.IsDir() {
2✔
226
                pathInZip += "/"
1✔
227
        }
1✔
228
        return pathInZip
1✔
229
}
230

231
func symlinkReferencesPredecessor(path string, predecessors map[string]bool) bool {
1✔
232
        _, ok := predecessors[path]
1✔
233
        return ok
1✔
234
}
1✔
235

236
func dereferenceSymlink(path string, predecessors map[string]bool) (string, os.FileInfo, []string, error) {
1✔
237

1✔
238
        var paths []string
1✔
239
        var linkedInfo os.FileInfo
1✔
240
        var linkedPath string
1✔
241
        var err error
1✔
242

1✔
243
        currentPath := path
1✔
244
        isSymlink := true
1✔
245
        for isSymlink {
2✔
246
                predecessors[currentPath] = true
1✔
247
                paths = append(paths, currentPath)
1✔
248
                // get path that symbolic link points to
1✔
249
                linkedPath, err = fileInfoProvider.readlink(currentPath)
1✔
250
                if err != nil {
2✔
251
                        return "", nil, nil, errors.Wrapf(err, badSymLink, currentPath)
1✔
252
                }
1✔
253

254
                if symlinkReferencesPredecessor(linkedPath, predecessors) {
2✔
255
                        return "", nil, nil, errors.Errorf(recursiveSymLinkMsg, linkedPath)
1✔
256
                }
1✔
257

258
                // Resolve relative path
259
                if !filepath.IsAbs(linkedPath) {
2✔
260
                        linkedPath = filepath.Join(filepath.Dir(currentPath), linkedPath)
1✔
261
                }
1✔
262

263
                linkedInfo, err = fileInfoProvider.stat(linkedPath)
1✔
264
                if err != nil {
2✔
265
                        return "", nil, nil, errors.Wrapf(err, badSymLink, currentPath)
1✔
266
                }
1✔
267
                if !fileInfoProvider.isSymbolicLink(linkedInfo) {
2✔
268
                        isSymlink = false
1✔
269
                } else {
2✔
270
                        currentPath = linkedPath
1✔
271
                }
1✔
272
        }
273
        return linkedPath, linkedInfo, paths, nil
1✔
274
}
275

276
func addSymbolicLinkToArchive(path string, baseDir, parentSymLinkPath, parentLinkedPath string, archive *zip.Writer,
277
        predecessors map[string]bool, ignore map[string]interface{}) (e error) {
1✔
278

1✔
279
        if symlinkReferencesPredecessor(path, predecessors) {
2✔
280
                return errors.Errorf(recursiveSymLinkMsg, path)
1✔
281
        }
1✔
282

283
        linkedPath, linkedInfo, paths, err := dereferenceSymlink(path, predecessors)
1✔
284

1✔
285
        if err != nil {
2✔
286
                return err
1✔
287
        }
1✔
288

289
        pathInZip := getPathInZip(path, baseDir, parentSymLinkPath, parentLinkedPath, linkedInfo)
1✔
290

1✔
291
        if !fileInfoProvider.isDir(linkedInfo) || filepath.Clean(path) != filepath.Clean(baseDir) {
2✔
292
                err = addToArchive(linkedPath, pathInZip, linkedInfo, archive)
1✔
293
                if err != nil {
1✔
294
                        return err
×
295
                }
×
296
        }
297

298
        if fileInfoProvider.isDir(linkedInfo) {
2✔
299
                files, err := ioutil.ReadDir(linkedPath)
1✔
300
                if err != nil {
1✔
301
                        return err
×
302
                }
×
303
                for _, file := range files {
2✔
304
                        err = walk(filepath.Join(linkedPath, file.Name()), baseDir, pathInZip, linkedPath, archive, predecessors, ignore)
1✔
305
                        if err != nil {
2✔
306
                                return err
1✔
307
                        }
1✔
308
                }
309
        }
310
        deleteAddedPredecessors(predecessors, paths)
1✔
311

1✔
312
        return nil
1✔
313
}
314

315
func deleteAddedPredecessors(predecessors map[string]bool, paths []string) {
1✔
316
        for _, currentPath := range paths {
2✔
317
                delete(predecessors, currentPath)
1✔
318
        }
1✔
319
}
320

321
func addToArchive(path string, pathInZip string, info os.FileInfo, archive *zip.Writer) (e error) {
1✔
322
        header, err := zip.FileInfoHeader(info)
1✔
323
        if err != nil {
1✔
324
                return err
×
325
        }
×
326

327
        header.Name = pathInZip
1✔
328
        if !info.IsDir() {
2✔
329
                header.Method = zip.Deflate
1✔
330
        }
1✔
331

332
        // add new header and file to archive
333
        writer, err := archive.CreateHeader(header)
1✔
334
        if err != nil {
1✔
335
                return err
×
336
        }
×
337

338
        if info.IsDir() {
2✔
339
                return nil
1✔
340
        }
1✔
341

342
        file, err := os.Open(path)
1✔
343
        if err != nil {
1✔
344
                return err
×
345
        }
×
346
        defer func() {
2✔
347
                e = CloseFile(file, e)
1✔
348
        }()
1✔
349

350
        _, err = io.Copy(writer, file)
1✔
351

1✔
352
        return err
1✔
353
}
354

355
// CreateFile - create new file
356
func CreateFile(path string) (file *os.File, err error) {
1✔
357
        file, err = os.Create(path) // Truncates if file already exists
1✔
358
        if err != nil {
2✔
359
                return nil, errors.Wrapf(err, fileCreationFailedMsg, path)
1✔
360
        }
1✔
361
        // The caller needs to use defer.close
362
        return file, err
1✔
363
}
364

365
// CopyDir - copy directory content
366
func CopyDir(src string, dst string, withParents bool, copyDirEntries func(entries []os.FileInfo, src, dst string) error) error {
1✔
367
        src = filepath.Clean(src)
1✔
368
        dst = filepath.Clean(dst)
1✔
369

1✔
370
        _, err := os.Stat(dst)
1✔
371
        if err != nil && !os.IsNotExist(err) {
2✔
372
                return err
1✔
373
        }
1✔
374

375
        if !withParents && err != nil {
2✔
376
                err = os.Mkdir(dst, os.ModePerm)
1✔
377
        } else if err != nil {
3✔
378
                err = CreateDirIfNotExist(dst)
1✔
379
        }
1✔
380
        if err != nil {
1✔
381
                return err
×
382
        }
×
383

384
        entries, err := ioutil.ReadDir(src)
1✔
385
        if err != nil {
2✔
386
                return err
1✔
387
        }
1✔
388
        return copyDirEntries(entries, src, dst)
1✔
389
}
390

391
// FindPath returns the path or its first match in case it's a pattern. If the path doesn't exist an error is returned.
392
func FindPath(path string) (string, error) {
1✔
393
        sourceEntries, err := filepath.Glob(path)
1✔
394
        if err == nil && len(sourceEntries) > 0 {
2✔
395
                // Return the first result sorted alphabetically.
1✔
396
                // Sorting is required to make sure the result is consistent upon several calls.
1✔
397
                sort.Strings(sourceEntries)
1✔
398
                return sourceEntries[0], nil
1✔
399
        }
1✔
400
        if err != nil {
2✔
401
                return "", errors.Wrapf(err, wrongPathMsg, path)
1✔
402
        }
1✔
403
        return "", errors.Errorf(wrongPathMsg, path)
1✔
404
}
405

406
func FindFile(root string, filename string) (string, error) {
×
407
        var foundPath string
×
408
        err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
×
409
                if err != nil {
×
410
                        return err
×
411
                }
×
412
                if !info.IsDir() && info.Name() == filename {
×
413
                        foundPath = path
×
414
                        return filepath.SkipDir
×
415
                }
×
416
                return nil
×
417
        })
418
        if err != nil {
×
419
                return "", err
×
420
        }
×
421
        if foundPath == "" {
×
422
                return "", fmt.Errorf("%s not found in %s", filename, root)
×
423
        }
×
424
        return foundPath, nil
×
425
}
426

427
// CopyByPatterns - copy files/directories according to patterns
428
// from source folder to target folder
429
// patterns are relative to source folder
430
func CopyByPatterns(source, target string, patterns []string) error {
1✔
431

1✔
432
        if len(patterns) == 0 {
2✔
433
                return nil
1✔
434
        }
1✔
435

436
        logs.Logger.Infof(copyByPatternMsg, patterns[0], source, target)
1✔
437

1✔
438
        // Resolve the source pattern if necessary
1✔
439
        source, err := FindPath(source)
1✔
440
        if err != nil {
2✔
441
                return err
1✔
442
        }
1✔
443

444
        infoTargetDir, err := os.Stat(target)
1✔
445
        if err != nil {
2✔
446
                err = CreateDirIfNotExist(target)
1✔
447
                if err != nil {
2✔
448
                        return errors.Wrapf(err, copyByPatternFailedOnCreateMsg, patterns[0], source, target, target)
1✔
449
                }
1✔
450
                logs.Logger.Infof(folderCreatedMsg, target)
1✔
451

452
        } else if !infoTargetDir.IsDir() {
2✔
453
                return errors.Errorf(copyByPatternFailedOnTargetMsg, patterns[0], source, target, target)
1✔
454
        }
1✔
455

456
        for _, pattern := range patterns {
2✔
457
                err = copyByPattern(source, target, pattern)
1✔
458
                if err != nil {
2✔
459
                        return err
1✔
460
                }
1✔
461
        }
462

463
        return nil
1✔
464
}
465

466
// copyByPattern - copy files/directories according to pattern
467
func copyByPattern(source, target, pattern string) error {
1✔
468
        logs.Logger.Infof(`copying the "%s" pattern from the "%s" folder to the "%s" folder`,
1✔
469
                pattern, source, target)
1✔
470
        // Check if the source is a file or a folder. If it's a file, the pattern "*" should copy the file itself.
1✔
471
        info, err := os.Stat(source)
1✔
472
        if err != nil {
1✔
473
                return err
×
474
        }
×
475
        var fullPattern string
1✔
476
        if !info.IsDir() && pattern == "*" {
2✔
477
                fullPattern = source
1✔
478
        } else {
2✔
479
                // build full pattern concatenating source path and pattern
1✔
480
                fullPattern = filepath.Join(source, strings.Replace(pattern, "./", "", -1))
1✔
481
        }
1✔
482
        // get all entries matching the pattern
483
        sourceEntries, err := filepath.Glob(fullPattern)
1✔
484
        if err != nil {
2✔
485
                return errors.Wrapf(err, copyByPatternFailedOnMatchMsg, pattern, source, target, pattern)
1✔
486
        }
1✔
487

488
        err = copyEntries(sourceEntries, source, target, pattern)
1✔
489
        if err != nil {
2✔
490
                return err
1✔
491
        }
1✔
492

493
        return nil
1✔
494
}
495

496
func copyEntries(entries []string, source, target, pattern string) error {
1✔
497
        for _, entry := range entries {
2✔
498
                info, err := os.Stat(entry)
1✔
499
                if err != nil {
2✔
500
                        return errors.Wrapf(err, copyFailedOnGetStatusMsg, pattern, source, target, entry)
1✔
501
                }
1✔
502
                targetEntry := filepath.Join(target, filepath.Base(entry))
1✔
503
                if info.IsDir() {
2✔
504
                        err = CopyDir(entry, targetEntry, true, CopyEntries)
1✔
505
                } else {
2✔
506
                        err = CopyFileWithMode(entry, targetEntry, info.Mode())
1✔
507
                }
1✔
508
                if err != nil {
2✔
509
                        return errors.Wrapf(err, copyFailedMsg, pattern, source, target, entry, targetEntry)
1✔
510
                }
1✔
511
        }
512
        return nil
1✔
513
}
514

515
// CopyEntries - copies entries (files and directories) from source to destination folder
516
func CopyEntries(entries []os.FileInfo, src, dst string) error {
1✔
517

1✔
518
        if len(entries) == 0 {
2✔
519
                return nil
1✔
520
        }
1✔
521
        for _, entry := range entries {
2✔
522
                var err error
1✔
523
                srcPath := filepath.Join(src, entry.Name())
1✔
524
                dstPath := filepath.Join(dst, entry.Name())
1✔
525

1✔
526
                if entry.IsDir() {
2✔
527
                        // execute recursively
1✔
528
                        err = CopyDir(srcPath, dstPath, false, CopyEntries)
1✔
529
                } else {
2✔
530
                        // Todo check posix compatibility
1✔
531
                        if entry.Mode()&os.ModeSymlink != 0 {
2✔
532
                                logs.Logger.Infof(skipSymbolicLinkMsg, src, dst, entry.Name())
1✔
533
                        } else {
2✔
534
                                err = CopyFileWithMode(srcPath, dstPath, entry.Mode())
1✔
535
                        }
1✔
536
                }
537
                if err != nil {
2✔
538
                        return err
1✔
539
                }
1✔
540
        }
541
        return nil
1✔
542
}
543

544
// CopyEntriesInParallel - copies entries (files and directories) from source to destination folder in parallel
545
func CopyEntriesInParallel(entries []os.FileInfo, src, dst string) (rerr error) {
1✔
546

1✔
547
        // limit parallel processes
1✔
548
        const maxOpenFiles = 5
1✔
549

1✔
550
        if len(entries) == 0 {
2✔
551
                return nil
1✔
552
        }
1✔
553
        // handle parallel processes with limited slice of semaphores
554
        sem := make(chan bool, maxOpenFiles)
1✔
555
        for _, entry := range entries {
2✔
556
                // if copy failed stop processing
1✔
557
                if rerr != nil {
1✔
558
                        break
×
559
                }
560
                sem <- true
1✔
561
                go func(e os.FileInfo) {
2✔
562

1✔
563
                        // free place in semaphores at the end of routine
1✔
564
                        defer func() { <-sem }()
2✔
565

566
                        var err error
1✔
567
                        srcPath := filepath.Join(src, e.Name())
1✔
568
                        dstPath := filepath.Join(dst, e.Name())
1✔
569

1✔
570
                        if e.IsDir() {
2✔
571
                                // execute recursively
1✔
572
                                err = CopyDir(srcPath, dstPath, false, CopyEntriesInParallel)
1✔
573
                        } else {
2✔
574
                                // Todo check posix compatibility
1✔
575
                                if e.Mode()&os.ModeSymlink != 0 {
2✔
576
                                        logs.Logger.Infof(skipSymbolicLinkMsg, src, dst, e.Name())
1✔
577
                                } else {
2✔
578
                                        err = CopyFileWithMode(srcPath, dstPath, e.Mode())
1✔
579
                                }
1✔
580
                        }
581
                        if err != nil {
2✔
582
                                rerr = err
1✔
583
                        }
1✔
584
                }(entry)
585
        }
586
        // wait for the end of all running go routines
587
        for i := 0; i < cap(sem); i++ {
2✔
588
                sem <- true
1✔
589
        }
1✔
590
        return
1✔
591
}
592

593
// CopyFileWithMode - copy file content using file mode parameter
594
func CopyFileWithMode(src, dst string, mode os.FileMode) (rerr error) {
1✔
595
        in, err := os.Open(src)
1✔
596
        if err != nil {
2✔
597
                return err
1✔
598
        }
1✔
599
        defer func() {
2✔
600
                rerr = CloseFile(in, rerr)
1✔
601
        }()
1✔
602

603
        out, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
1✔
604
        if err != nil {
2✔
605
                return err
1✔
606
        }
1✔
607
        defer func() {
2✔
608
                rerr = CloseFile(out, rerr)
1✔
609
        }()
1✔
610

611
        _, err = io.Copy(out, in)
1✔
612
        if err != nil {
2✔
613
                return err
1✔
614
        }
1✔
615

616
        return err
1✔
617

618
}
619

620
// CopyFile - copy file content
621
func CopyFile(src, dst string) (rerr error) {
1✔
622
        in, err := os.Open(src)
1✔
623
        if err != nil {
2✔
624
                return err
1✔
625
        }
1✔
626
        defer func() {
2✔
627
                rerr = CloseFile(in, rerr)
1✔
628
        }()
1✔
629

630
        err = WriteFile(in, dst)
1✔
631
        if err != nil {
2✔
632
                return err
1✔
633
        }
1✔
634
        return changeTargetMode(src, dst)
1✔
635
}
636

637
// WriteFile - writes file content
638
func WriteFile(in io.Reader, dst string) (rerr error) {
1✔
639
        out, err := os.Create(dst)
1✔
640
        if err != nil {
2✔
641
                return err
1✔
642
        }
1✔
643
        defer func() {
2✔
644
                rerr = CloseFile(out, rerr)
1✔
645
        }()
1✔
646

647
        _, err = io.Copy(out, in)
1✔
648
        return err
1✔
649
}
650

651
func changeTargetMode(source, target string) error {
1✔
652
        si, err := os.Stat(source)
1✔
653
        if err != nil {
2✔
654
                return err
1✔
655
        }
1✔
656

657
        return os.Chmod(target, si.Mode())
1✔
658
}
659

660
// getRelativePath - Remove the basePath from the fullPath and get only the relative
661
func getRelativePath(fullPath, basePath string) string {
1✔
662
        if basePath == "" || !strings.HasPrefix(fullPath, basePath) {
2✔
663
                return fullPath
1✔
664
        }
1✔
665
        return strings.TrimPrefix(strings.TrimPrefix(fullPath, basePath), string(filepath.Separator))
1✔
666
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc