• 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

68.94
/internal/artifacts/sbom.go
1
package artifacts
2

3
import (
4
        "io/ioutil"
5
        "os"
6
        "path/filepath"
7
        "strconv"
8
        "strings"
9
        "time"
10

11
        dir "github.com/SAP/cloud-mta-build-tool/internal/archive"
12
        "github.com/SAP/cloud-mta-build-tool/internal/buildops"
13
        "github.com/SAP/cloud-mta-build-tool/internal/commands"
14
        "github.com/SAP/cloud-mta-build-tool/internal/exec"
15
        "github.com/SAP/cloud-mta-build-tool/internal/logs"
16
        "github.com/SAP/cloud-mta-build-tool/internal/version"
17
        "github.com/SAP/cloud-mta/mta"
18
        "github.com/pkg/errors"
19
)
20

21
const (
22
        xml_type         = "xml"
23
        json_type        = "json"
24
        unsupport_type   = "unsupport_sbom_type"
25
        xml_suffix       = ".xml"
26
        json_suffix      = ".json"
27
        sbom_xml_suffix  = ".bom.xml"
28
        sbom_json_suffix = ".bom.json"
29
        cyclonedx_cli    = "cyclonedx"
30
)
31

32
// ExecuteProjectSBomGenerate - Execute MTA project SBOM generation
33
func ExecuteProjectSBomGenerate(source string, sbomFilePath string, wdGetter func() (string, error)) error {
1✔
34
        // (1) get loc object and mta object
1✔
35
        loc, err := dir.Location(source, "", dir.Dev, []string{}, wdGetter)
1✔
36
        if err != nil {
1✔
37
                return errors.Wrapf(err, genSBomFileFailedMsg)
×
38
        }
×
39

40
        mtaObj, err := loc.ParseFile()
1✔
41
        if err != nil {
2✔
42
                return errors.Wrapf(err, genSBomFileFailedMsg)
1✔
43
        }
1✔
44

45
        // (2) if sbom file path is empty, default value is <MTA project path>/<MTA project id>.bom.xml
46
        if strings.TrimSpace(sbomFilePath) == "" {
2✔
47
                sbomFilePath = mtaObj.ID + sbom_xml_suffix
1✔
48
        }
1✔
49

50
        // (3) generate sbom
51
        err = executeSBomGenerate(loc, mtaObj, source, sbomFilePath)
1✔
52
        if err != nil {
2✔
53
                return errors.Wrapf(err, genSBomFileFailedMsg)
1✔
54
        }
1✔
55

56
        return nil
1✔
57
}
58

59
// ExecuteProjectBuildeSBomGenerate - Execute MTA project SBOM generation with Build process
60
func ExecuteProjectBuildeSBomGenerate(source string, sbomFilePath string, wdGetter func() (string, error)) error {
1✔
61
        // (1) if sbomFilePath is empty, do not need to generate sbom, return directly
1✔
62
        if strings.TrimSpace(sbomFilePath) == "" {
2✔
63
                return nil
1✔
64
        }
1✔
65

66
        // (2) get loc object and mta object
67
        loc, err := dir.Location(source, "", dir.Dev, []string{}, wdGetter)
1✔
68
        if err != nil {
1✔
69
                return errors.Wrapf(err, genSBomFileFailedMsg)
×
70
        }
×
71

72
        mtaObj, err := loc.ParseFile()
1✔
73
        if err != nil {
2✔
74
                return errors.Wrapf(err, genSBomFileFailedMsg)
1✔
75
        }
1✔
76

77
        // (3) generate sbom
78
        err = executeSBomGenerate(loc, mtaObj, source, sbomFilePath)
1✔
79
        if err != nil {
2✔
80
                return errors.Wrapf(err, genSBomFileFailedMsg)
1✔
81
        }
1✔
82

83
        return nil
1✔
84
}
85

86
// prepareEnv - create sbom tmp dir and sbom target dir
87
// Notice: must not remove the sbom target dir, if the sbom-file is generated under project root, remove sbom path will delete app
88
func prepareEnv(sbomTmpDir, sbomPath string) error {
1✔
89
        err := dir.RemoveIfExist(sbomTmpDir)
1✔
90
        if err != nil {
1✔
91
                return err
×
92
        }
×
93
        err = dir.CreateDirIfNotExist(sbomTmpDir)
1✔
94
        if err != nil {
1✔
95
                return err
×
96
        }
×
97
        err = dir.CreateDirIfNotExist(sbomPath)
1✔
98
        if err != nil {
1✔
99
                return err
×
100
        }
×
101

102
        return nil
1✔
103
}
104

105
// cleanEnv - clean sbom tmp dir
106
func cleanEnv(sbomTmpDir string) error {
1✔
107
        err := dir.RemoveIfExist(sbomTmpDir)
1✔
108
        if err != nil {
1✔
109
                return err
×
110
        }
×
111
        return nil
1✔
112
}
113

114
// generateSBomFile - generate all modules sbom and merge in to one, then mv it to sbom target path
115
func generateSBomFile(loc *dir.Loc, mtaObj *mta.MTA,
116
        sbomPath, sbomName, sbomType, sbomSuffix, sbomTmpDir string) error {
1✔
117
        // (1) generation sbom for modules under sbom tmp dir
1✔
118
        err := generateSBomFiles(loc, mtaObj, sbomTmpDir, sbomType, sbomSuffix)
1✔
119
        if err != nil {
1✔
120
                return err
×
121
        }
×
122

123
        // (2) list all generated module sbom file in tmp
124
        sbomFileNames, err := listSBomFilesInTmpDir(sbomTmpDir, sbomSuffix)
1✔
125
        if err != nil {
1✔
126
                return err
×
127
        }
×
128
        // if sbom tmp dir is empty, maybe all modules are unknow builder or custom builders
129
        if len(sbomFileNames) == 0 {
1✔
130
                logs.Logger.Infof(genSBomEmptyMsg, sbomName)
×
131
                return nil
×
132
        }
×
133

134
        // (3) merge sbom files under sbom tmp dir
135
        sbomTmpName, err := mergeSBomFiles(loc, sbomTmpDir, sbomFileNames, sbomName, sbomType, sbomSuffix)
1✔
136
        if err != nil {
1✔
137
                return err
×
138
        }
×
139

140
        // (4) generate sbom target dir, mv merged sbom file to target dir
141
        err = moveSBomToTarget(sbomPath, sbomName, sbomTmpDir, sbomTmpName)
1✔
142
        if err != nil {
1✔
143
                return err
×
144
        }
×
145

146
        return nil
1✔
147
}
148

149
func executeSBomGenerate(loc *dir.Loc, mtaObj *mta.MTA, source string, sbomFilePath string) error {
1✔
150
        // start generate sbom file log
1✔
151
        logs.Logger.Info(genSBomFileStartMsg)
1✔
152

1✔
153
        // (1) parse sbomFilePath, if relative, it is relative path to project source
1✔
154
        // json type sbom file is not supported at present, if sbom file type is json, return not support error
1✔
155
        sbomPath, sbomName, sbomType, sbomSuffix := parseSBomFilePath(loc.GetSource(), sbomFilePath)
1✔
156
        if sbomType == unsupport_type {
2✔
157
                return errors.Errorf(genSBomNotSupportedFileTypeMsg, sbomSuffix)
1✔
158
        }
1✔
159

160
        // (2) create sbom tmp dir and sbom target path
161
        sbomTmpDir := loc.GetSBomFileTmpDir(mtaObj)
1✔
162
        prepareErr := prepareEnv(sbomTmpDir, sbomPath)
1✔
163
        if prepareErr != nil {
1✔
164
                return prepareErr
×
165
        }
×
166

167
        // (3) generate sbom file
168
        genError := generateSBomFile(loc, mtaObj, sbomPath, sbomName, sbomType, sbomSuffix, sbomTmpDir)
1✔
169
        if genError != nil {
1✔
170
                cleanErr := cleanEnv(sbomTmpDir)
×
171
                if cleanErr != nil {
×
172
                        logs.Logger.Error(cleanErr)
×
173
                }
×
174
                return genError
×
175
        }
176

177
        // (4) clean sbom tmp dir
178
        cleanErr := cleanEnv(sbomTmpDir)
1✔
179
        if cleanErr != nil {
1✔
180
                return cleanErr
×
181
        }
×
182

183
        // finish generate sbom file log
184
        logs.Logger.Infof(genSBomFileFinishedMsg, sbomName)
1✔
185

1✔
186
        return nil
1✔
187
}
188

189
// moveSBomToTarget - move sbom file from sbom tmp dir to target dir
190
func moveSBomToTarget(sbomPath string, sbomName string, sbomTmpDir string, sbomTmpName string) error {
1✔
191
        err := dir.CreateDirIfNotExist(sbomPath)
1✔
192
        if err != nil {
1✔
193
                return errors.Wrapf(err, createSBomTargetDirFailedMsg, sbomName)
×
194
        }
×
195

196
        sourcesbomfilepath := filepath.Join(sbomTmpDir, sbomTmpName)
1✔
197
        targetsbomfilepath := filepath.Join(sbomPath, sbomName)
1✔
198

1✔
199
        err = os.Rename(sourcesbomfilepath, targetsbomfilepath)
1✔
200
        if err != nil {
1✔
201
                return errors.Wrapf(err, mvSBomToTargetDirFailedMsg, sourcesbomfilepath, targetsbomfilepath)
×
202
        }
×
203
        return nil
1✔
204
}
205

206
// listSBomFilesInTmpDir - list generated sbom files for modules
207
// if sbom tmp dir is empty, return empty array
208
func listSBomFilesInTmpDir(sbomTmpDir, sbomSuffix string) ([]string, error) {
1✔
209
        var sbomFileNames []string
1✔
210
        fileInfos, err := ioutil.ReadDir(sbomTmpDir)
1✔
211
        if err != nil {
1✔
212
                return sbomFileNames, err
×
213
        }
×
214

215
        for _, file := range fileInfos {
2✔
216
                fileName := file.Name()
1✔
217
                if !file.IsDir() && len(fileName) > 0 && strings.HasSuffix(fileName, sbomSuffix) {
2✔
218
                        sbomFileNames = append(sbomFileNames, fileName)
1✔
219
                }
1✔
220
        }
221
        return sbomFileNames, nil
1✔
222
}
223

224
// mergeSBomFiles - merge sbom files of modules under sbom tmp dir
225
func mergeSBomFiles(loc *dir.Loc, sbomTmpDir string, sbomFileNames []string, sbomName, sbomType, sbomSuffix string) (string, error) {
1✔
226
        curtime := time.Now().Format("20230328150313")
1✔
227

1✔
228
        var sbomTmpName string
1✔
229
        if strings.HasSuffix(sbomName, sbom_xml_suffix) {
2✔
230
                sbomTmpName = strings.TrimSuffix(sbomName, xml_suffix) + "_" + curtime + sbom_xml_suffix
1✔
231
        } else if strings.HasSuffix(sbomName, sbom_json_suffix) {
2✔
232
                sbomTmpName = strings.TrimSuffix(sbomName, json_suffix) + "_" + curtime + sbom_json_suffix
×
233
        } else {
1✔
234
                sbomTmpName = sbomName + "_" + curtime + sbom_xml_suffix
1✔
235
        }
1✔
236

237
        // get sbom file generate command
238
        sbomMergeCmds, err := commands.GetSBomsMergeCommand(loc, cyclonedx_cli, sbomTmpDir, sbomFileNames, sbomTmpName, sbomType, sbomSuffix)
1✔
239
        if err != nil {
1✔
240
                return "", err
×
241
        }
×
242

243
        // merging sbom file log
244
        logs.Logger.Infof(genSBomFileMergingMsg, sbomName)
1✔
245

1✔
246
        // exec sbom merge command
1✔
247
        err = executeSBomCommand(sbomMergeCmds)
1✔
248
        if err != nil {
1✔
249
                return "", err
×
250
        }
×
251
        return sbomTmpName, nil
1✔
252
}
253

254
// parseSBomFilePath - parse sbom file path parameter
255
// if sbom file path is a relative path, join source path
256
// only xml file format is supported at present;
257
func parseSBomFilePath(source string, sbomFilePath string) (string, string, string, string) {
1✔
258
        var sbomPath, sbomName, sbomType, sbomSuffix string
1✔
259

1✔
260
        if filepath.IsAbs(sbomFilePath) {
2✔
261
                sbomPath, sbomName = filepath.Split(sbomFilePath)
1✔
262
        } else {
2✔
263
                sbomPath, sbomName = filepath.Split(filepath.Join(source, sbomFilePath))
1✔
264
        }
1✔
265

266
        // if file suffix is .xml, or no file suffix, xml format type will be return
267
        // if file suffix is not .xml, unsupported file type will be return
268
        fileSuffix := filepath.Ext(sbomName)
1✔
269
        if fileSuffix == "" || strings.HasSuffix(sbomName, xml_suffix) {
2✔
270
                sbomType = xml_type
1✔
271
                sbomSuffix = sbom_xml_suffix
1✔
272
        } else {
2✔
273
                sbomType = unsupport_type
1✔
274
                sbomSuffix = fileSuffix
1✔
275
        }
1✔
276

277
        return sbomPath, sbomName, sbomType, sbomSuffix
1✔
278
}
279

280
func executeSBomCommand(sbomCmds [][]string) error {
1✔
281
        err := exec.ExecuteWithTimeout(sbomCmds, "", true)
1✔
282
        if err != nil {
1✔
283
                return err
×
284
        }
×
285
        return nil
1✔
286
}
287

288
// generateSBomFiles - loop all mta modules and generate sbom for each of then
289
// if module's builder is custom, skip it
290
func generateSBomFiles(loc *dir.Loc, mtaObj *mta.MTA, sBomFileTmpDir string, sbomType string, sbomSuffix string) error {
1✔
291
        // (1) sort module by dependency orders
1✔
292
        sortedModuleNames, err := buildops.GetModulesNames(mtaObj)
1✔
293
        if err != nil {
1✔
294
                return err
×
295
        }
×
296

297
        // (2) loop modules to generate sbom files
298
        curtime := time.Now().Format("20230328150313")
1✔
299
        for _, moduleName := range sortedModuleNames {
2✔
300
                // start generate module sbom log
1✔
301
                logs.Logger.Infof(genSBomForModuleStartMsg, moduleName)
1✔
302

1✔
303
                module, err := mtaObj.GetModuleByName(moduleName)
1✔
304
                if err != nil {
1✔
305
                        return err
×
306
                }
×
307

308
                sbomFileName := moduleName + "_" + curtime
1✔
309
                sbomFileFullName := sbomFileName + sbomSuffix
1✔
310

1✔
311
                // get sbom file generate command
1✔
312
                sbomGenCmds, err := commands.GetModuleSBomGenCommands(loc, module, sbomFileName, sbomType, sbomSuffix)
1✔
313
                if err != nil {
1✔
314
                        return err
×
315
                }
×
316
                // if sbomGenCmds is empty, module builder maybe "custom" or unknow builder, skip the module and continue
317
                if len(sbomGenCmds) == 0 {
1✔
318
                        logs.Logger.Infof(genSBomSkipModuleMsg, moduleName)
×
319
                        continue
×
320
                }
321

322
                // exec sbom generate command
323
                err = executeSBomCommand(sbomGenCmds)
1✔
324
                if err != nil {
1✔
325
                        return err
×
326
                }
×
327

328
                // mv module sbom file to sbom temp dir
329
                modulePath := loc.GetSourceModuleDir(module.Path)
1✔
330
                sbomFileFoundPath, err := dir.FindFile(modulePath, sbomFileFullName)
1✔
331
                if err != nil {
1✔
332
                        return err
×
333
                }
×
334
                sbomFileTargetPath := filepath.Join(sBomFileTmpDir, sbomFileFullName)
1✔
335
                err = os.Rename(sbomFileFoundPath, sbomFileTargetPath)
1✔
336
                if err != nil {
1✔
337
                        return err
×
338
                }
×
339

340
                // finish generate module sbom log
341
                logs.Logger.Infof(genSBomForModuleFinishMsg, moduleName)
1✔
342
        }
343

344
        return nil
1✔
345
}
346

347
// ExecuteModuleSBomGenerate - Execute specified modules of MTA project SBOM generation
348
func ExecuteModuleSBomGenerate(source string, modulesNames []string, allDependencies bool, sBomFilePath string, wdGetter func() (string, error)) error {
×
349
        logs.Logger.Info("source: " + source)
×
350

×
351
        for _, moduleName := range modulesNames {
×
352
                logs.Logger.Info("module: " + moduleName)
×
353
        }
×
354

355
        logs.Logger.Info("allDependencies: " + strconv.FormatBool(allDependencies))
×
356
        logs.Logger.Info("sBomFilePath: " + sBomFilePath)
×
357

×
358
        message, err := version.GetVersionMessage()
×
359
        if err == nil {
×
360
                logs.Logger.Info(message)
×
361
        }
×
362
        return err
×
363
}
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