• 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

54.69
/internal/commands/commands.go
1
package commands
2

3
import (
4
        "fmt"
5
        "strings"
6

7
        "github.com/kballard/go-shellquote"
8
        "github.com/pkg/errors"
9

10
        "github.com/SAP/cloud-mta/mta"
11

12
        "github.com/SAP/cloud-mta-build-tool/internal/archive"
13
        "github.com/SAP/cloud-mta-build-tool/internal/logs"
14
)
15

16
const (
17
        builderParam  = "builder"
18
        commandsParam = "commands"
19
        customBuilder = "custom"
20
        golangBuilder = "golang"
21
        optionsSuffix = "-opts"
22
        goModuleType  = "go"
23
)
24

25
// CommandList - list of command to execute
26
type CommandList struct {
27
        Info    string
28
        Command []string
29
}
30

31
// GetBuilder - gets builder type of the module and indicator of custom builder
32
// if build-parameter == null or build-parameter.builder == null, return builder=module.type and custom=false
33
// else if build-paramete.builder != custom, return builder=build-paramete.builder and custom=true
34
// else if build-paramete.builder == custom, return builder=custom and custom=true
35
func GetBuilder(module *mta.Module) (string, bool, map[string]string, []string, error) {
1✔
36
        // builder defined in build params is prioritised
1✔
37
        if module.BuildParams != nil && module.BuildParams[builderParam] != nil {
2✔
38
                builderName := module.BuildParams[builderParam].(string)
1✔
39
                checkDeprecatedBuilder(builderName)
1✔
40
                optsParamName := builderName + optionsSuffix
1✔
41
                // get options for builder from mta.yaml
1✔
42
                options := getOpts(module, optsParamName)
1✔
43
                var cmds []string
1✔
44
                if builderName == customBuilder {
2✔
45
                        cmdsParam, ok := module.BuildParams[commandsParam]
1✔
46
                        if !ok {
2✔
47
                                logs.Logger.Warn(missingPropMsg)
1✔
48
                                return builderName, true, options, []string{}, nil
1✔
49
                        }
1✔
50
                        cmds, ok = cmdsParam.([]string)
1✔
51
                        if !ok {
2✔
52
                                cmdsI, okI := cmdsParam.([]interface{})
1✔
53
                                if okI {
1✔
54
                                        ok = true
×
55
                                        for _, cmdI := range cmdsI {
×
56
                                                cmd, okCmd := cmdI.(string)
×
57
                                                if !okCmd {
×
58
                                                        ok = false
×
59
                                                        break
×
60
                                                }
61
                                                cmds = append(cmds, cmd)
×
62
                                        }
63
                                }
64
                        }
65
                        if !ok {
2✔
66
                                return builderName, true, options, cmds, fmt.Errorf(wrongPropMsg)
1✔
67
                        }
1✔
68
                }
69

70
                return builderName, true, options, cmds, nil
1✔
71
        }
72
        // default builder is defined by type property of the module
73
        return module.Type, false, nil, nil, nil
1✔
74
}
75

76
func isNativeBuilderType(builderName string) (bool, error) {
×
77
        builderTypes, err := parseBuilders(BuilderTypeConfig)
×
78
        if err != nil {
×
79
                return false, errors.Wrap(err, parseBuilderCfgFailedMsg)
×
80
        }
×
81

82
        for _, b := range builderTypes.Builders {
×
83
                if builderName == b.Name {
×
84
                        return true, err
×
85
                }
×
86
        }
87
        return false, err
×
88
}
89

90
func getSBomBuilderByModuleType(typeName string) (bool, string, error) {
×
91
        moduleTypes, err := parseModuleTypes(ModuleTypeConfig)
×
92
        if err != nil {
×
93
                return false, "", errors.Wrap(err, parseModuleCfgFailedMsg)
×
94
        }
×
95

96
        for _, t := range moduleTypes.ModuleTypes {
×
97
                if typeName == t.Name {
×
98
                        return true, t.Builder, nil
×
99
                }
×
100
        }
101

102
        // if module.type == go, return the golang builder;
103
        // Notice, there is no go type in ModuleTypeConfig(module_type_cfg.yaml)
104
        if typeName == goModuleType {
×
105
                return true, golangBuilder, nil
×
106
        }
×
107

108
        return false, "", errors.Wrapf(err, notNativeModuleTypeMsg, typeName)
×
109
}
110

111
func getModuleSBomBuilder(module *mta.Module) (string, error) {
×
112
        var builderName string
×
113
        var err error
×
114

×
115
        // get builder by build-parameter.builder
×
116
        if module.BuildParams != nil && module.BuildParams[builderParam] != nil {
×
117
                builderName = module.BuildParams[builderParam].(string)
×
118
                checkDeprecatedBuilder(builderName)
×
119
                if builderName == customBuilder {
×
120
                        _, ok := module.BuildParams[commandsParam]
×
121
                        if !ok {
×
122
                                return builderName, errors.Wrap(err, missingPropMsg)
×
123
                        }
×
124
                        return builderName, nil
×
125
                }
126

127
                // check if builder is native builder (builder_type_cfg.yaml)
128
                isnativebuilder, err := isNativeBuilderType(builderName)
×
129
                if !isnativebuilder {
×
130
                        return builderName, errors.Wrapf(err, notNativeBuilderMsg, builderName)
×
131
                }
×
132

133
                return builderName, nil
×
134
        }
135

136
        // get builder by module type
137
        isfind, builderName, err := getSBomBuilderByModuleType(module.Type)
×
138
        if !isfind {
×
139
                return "", err
×
140
        }
×
141
        return builderName, nil
×
142
}
143

144
// Get options for builder from mta.yaml
145
func getOpts(module *mta.Module, optsParamName string) map[string]string {
1✔
146
        options := module.BuildParams[optsParamName]
1✔
147
        optionsMap := make(map[string]string)
1✔
148
        if options != nil {
2✔
149
                optionsMapS, ok := options.(map[string]interface{})
1✔
150
                if !ok {
1✔
151
                        optionsMapS = ConvertMap(options.(map[interface{}]interface{}))
×
152
                }
×
153
                optionsMap = convert(optionsMapS)
1✔
154
        }
155

156
        return optionsMap
1✔
157
}
158

159
// Convert type map[string]interface{} to map[string]string
160
func convert(m map[string]interface{}) map[string]string {
1✔
161
        res := make(map[string]string)
1✔
162
        for strKey, value := range m {
2✔
163
                strValue := ""
1✔
164
                if value != nil {
2✔
165
                        strValue = value.(string)
1✔
166
                }
1✔
167
                res[strKey] = strValue
1✔
168
        }
169

170
        return res
1✔
171
}
172

173
// ConvertMap converts type map[interface{}]interface{} to map[string]interface{}
174
func ConvertMap(m map[interface{}]interface{}) map[string]interface{} {
×
175
        res := make(map[string]interface{})
×
176
        for key, value := range m {
×
177
                strKey := key.(string)
×
178
                res[strKey] = value
×
179
        }
×
180

181
        return res
×
182
}
183

184
// CommandProvider - Get build command's to execute
185
// noinspection GoExportedFuncWithUnexportedType
186
func CommandProvider(module mta.Module) (CommandList, string, error) {
1✔
187
        // Get config from ./commands_cfg.yaml as generated artifacts from source
1✔
188
        moduleTypes, err := parseModuleTypes(ModuleTypeConfig)
1✔
189
        if err != nil {
2✔
190
                return CommandList{}, "", errors.Wrap(err, parseModuleCfgFailedMsg)
1✔
191
        }
1✔
192
        builderTypes, err := parseBuilders(BuilderTypeConfig)
1✔
193
        if err != nil {
2✔
194
                return CommandList{}, "", errors.Wrap(err, parseBuilderCfgFailedMsg)
1✔
195
        }
1✔
196
        return mesh(&module, &moduleTypes, &builderTypes)
1✔
197
}
198

199
// Match the object according to type and provide the respective command
200
func mesh(module *mta.Module, moduleTypes *ModuleTypes, builderTypes *Builders) (CommandList, string, error) {
1✔
201
        // The object support deep struct for future use, can be simplified to flat object
1✔
202
        var cmds CommandList
1✔
203
        var cmdList []string
1✔
204
        var commands []Command
1✔
205
        var err error
1✔
206

1✔
207
        // get builder - module type name or custom builder if defined
1✔
208
        // and indicator if custom builder
1✔
209
        builder, custom, options, cmdList, err := GetBuilder(module)
1✔
210
        if err != nil {
2✔
211
                return CommandList{Command: []string{}}, "", err
1✔
212
        }
1✔
213

214
        // if module type used - get from module types configuration corresponding commands or custom builder if defined
215
        if !custom {
2✔
216
                for _, m := range moduleTypes.ModuleTypes {
2✔
217
                        if m.Name == builder {
2✔
218
                                if m.Builder != "" {
2✔
219
                                        // custom builder defined
1✔
220
                                        // check that no commands defined for module type
1✔
221
                                        if m.Commands != nil && len(m.Commands) > 0 {
2✔
222
                                                return cmds, "", fmt.Errorf(wrongModuleTypeDefMsg, m.Name)
1✔
223
                                        }
1✔
224
                                        // continue with custom builders search
225
                                        builder = m.Builder
1✔
226
                                        custom = true
1✔
227
                                } else {
1✔
228
                                        // get related information
1✔
229
                                        cmds.Info = m.Info
1✔
230
                                        commands = m.Commands
1✔
231
                                }
1✔
232
                        }
233
                }
234
        }
235

236
        buildResults := ""
1✔
237

1✔
238
        if custom {
2✔
239
                // custom builder used => get commands and info
1✔
240
                commands, cmds.Info, buildResults, err = getCustomCommandsByBuilder(builderTypes, builder, cmdList)
1✔
241
                if err != nil {
2✔
242
                        return cmds, "", err
1✔
243
                }
1✔
244
        }
245

246
        // prepare result
247
        cmds, buildResults = prepareMeshResult(cmds, buildResults, commands, options)
1✔
248
        return cmds, buildResults, nil
1✔
249
}
250

251
// prepare commands list - mesh result
252
func prepareMeshResult(cmds CommandList, buildResults string, commands []Command, options map[string]string) (CommandList, string) {
1✔
253
        for _, cmd := range commands {
2✔
254
                if options != nil {
2✔
255
                        cmd.Command = meshOpts(cmd.Command, options)
1✔
256
                }
1✔
257
                cmds.Command = append(cmds.Command, cmd.Command)
1✔
258
        }
259
        return cmds, buildResults
1✔
260
}
261

262
// Update command according to options arguments
263
func meshOpts(cmd string, options map[string]string) string {
1✔
264
        c := cmd
1✔
265
        for key, value := range options {
2✔
266
                c = strings.Replace(c, "{{"+key+"}}", value, -1)
1✔
267
        }
1✔
268
        return c
1✔
269
}
270

271
func getCustomCommandsByBuilder(customCommands *Builders, builder string, cmds []string) ([]Command, string, string, error) {
1✔
272
        if builder == customBuilder {
2✔
273
                var res []Command
1✔
274
                for _, cmd := range cmds {
2✔
275
                        res = append(res, Command{cmd})
1✔
276
                }
1✔
277
                return res, "", "", nil
1✔
278
        }
279

280
        for _, b := range customCommands.Builders {
2✔
281
                if builder == b.Name {
2✔
282
                        return b.Commands, b.Info, b.BuildResult, nil
1✔
283
                }
1✔
284
        }
285

286
        return nil, "", "", fmt.Errorf(undefinedBuilderMsg, builder)
1✔
287
}
288

289
// CmdConverter - path and commands to execute
290
func CmdConverter(mPath string, cmdList []string) ([][]string, error) {
1✔
291
        var cmd [][]string
1✔
292
        for i := 0; i < len(cmdList); i++ {
2✔
293
                split, err := shellquote.Split(cmdList[i])
1✔
294
                if err != nil {
2✔
295
                        return nil, errors.Wrapf(err, BadCommandMsg, cmdList[i])
1✔
296
                }
1✔
297
                cmd = append(cmd, append([]string{mPath}, split...))
1✔
298
        }
299
        return cmd, nil
1✔
300
}
301

302
// GetModuleAndCommands - Get module from mta.yaml and
303
// commands (with resolved paths) configured for the module type
304
func GetModuleAndCommands(loc dir.IMtaParser, module string) (*mta.Module, []string, string, error) {
1✔
305
        mtaObj, err := loc.ParseFile()
1✔
306
        if err != nil {
2✔
307
                return nil, nil, "", err
1✔
308
        }
1✔
309
        // Get module respective command's to execute
310
        return moduleCmd(mtaObj, module)
1✔
311
}
312

313
// Get commands for specific module type
314
func moduleCmd(mta *mta.MTA, moduleName string) (*mta.Module, []string, string, error) {
1✔
315
        for _, m := range mta.Modules {
2✔
316
                if m.Name == moduleName {
2✔
317
                        commandProvider, buildResults, err := CommandProvider(*m)
1✔
318
                        if err != nil {
2✔
319
                                return nil, nil, "", err
1✔
320
                        }
1✔
321
                        return m, commandProvider.Command, buildResults, nil
1✔
322
                }
323
        }
324
        return nil, nil, "", errors.Errorf(undefinedModuleMsg, moduleName)
1✔
325
}
326

327
// GetModuleSBomGenCommands - get sbom generate command for module
328
// if unknow sbom gen builder or custom builder, empty [][]string and nil error will be return
329
func GetModuleSBomGenCommands(loc *dir.Loc, module *mta.Module,
330
        sbomFileName string, sbomFileType string, sbomFileSuffix string) ([][]string, error) {
×
331
        var cmd string
×
332
        var cmds []string
×
333
        var commandList [][]string
×
334

×
335
        builder, err := getModuleSBomBuilder(module)
×
336
        if err != nil {
×
337
                return [][]string{}, err
×
338
        }
×
339

340
        switch builder {
×
341
        case "npm", "npm-ci", "grunt", "evo":
×
342
                cmd = "npm install"
×
343
                cmds = append(cmds, cmd)
×
344
                cmd = "npm install cyclonedx-bom@0.0.9 --no-save"
×
345
                cmds = append(cmds, cmd)
×
346
                cmd = "npx cyclonedx-bom -o " + sbomFileName + sbomFileSuffix
×
347
                cmds = append(cmds, cmd)
×
348
        case "golang":
×
349
                cmd = "cyclonedx-gomod mod -licenses -output " + sbomFileName + sbomFileSuffix
×
350
                cmds = append(cmds, cmd)
×
351
        case "maven", "fetcher", "maven_deprecated":
×
352
                cmd = "mvn org.cyclonedx:cyclonedx-maven-plugin:2.7.5:makeAggregateBom " +
×
353
                        "-DschemaVersion=1.2 -DincludeBomSerialNumber=true -DincludeCompileScope=true " +
×
354
                        "-DincludeRuntimeScope=true -DincludeSystemScope=true -DincludeTestScope=false -DincludeLicenseText=false " +
×
355
                        "-DoutputFormat=" + sbomFileType + " -DoutputName=" + sbomFileName + ".bom"
×
356
                cmds = append(cmds, cmd)
×
357
        case "custom":
×
358
        default:
×
359
        }
360

361
        modulePath := loc.GetSourceModuleDir(module.Path)
×
362
        commandList, err = CmdConverter(modulePath, cmds)
×
363
        if err != nil {
×
364
                return [][]string{}, err
×
365
        }
×
366
        return commandList, err
×
367
}
368

369
// GetSBomsMergeCommand - generate merge sbom file command under sbom tmp dir
370
// if empty sbomFileNames, return empty commandList, nil error
371
func GetSBomsMergeCommand(loc *dir.Loc, cyclonedx_cli string, sbomTmpDir string, sbomFileNames []string,
372
        sbomName, sbomType, sbomSuffix string) ([][]string, error) {
×
373
        var cmd string
×
374
        var cmds []string
×
375
        var commandList [][]string
×
376

×
377
        // len(sbomFileName) should not be 0, if 0 then raise an error
×
378
        if len(sbomFileNames) == 0 {
×
379
                return commandList, errors.New(emptySBomFileInputMsg)
×
380
        }
×
381

382
        var inputFiles string
×
383
        for _, fileName := range sbomFileNames {
×
384
                inputFiles = inputFiles + " " + fileName + " "
×
385
        }
×
386

387
        // ./cyclonedx merge --input-files test_1.bom.xml test_2.bom.xml test_3.bom.xml --output-file merged.bom.xml
388
        cmd = cyclonedx_cli + " merge --input-files " + inputFiles + " --output-file " + sbomName +
×
389
                " --input-format " + sbomType + " --output-format " + sbomType
×
390
        cmds = append(cmds, cmd)
×
391
        commandList, err := CmdConverter(sbomTmpDir, cmds)
×
392

×
393
        return commandList, err
×
394
}
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