1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-03-03 15:02:35 +02:00

feat(gradleExecuteBuild): BOM creation. Integration tests (#3603)

* Implemented bom creation

* Made small fixes. Added integration tests

* go generate

* minor fixes

* fix tests

* Added unit tests

* minor fixes

* use fileutils

* integration tests optimization

* change integraton tests timeout to 25m

* Fix Inclusive Language warnings

Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com>
This commit is contained in:
Siarhei Pazdniakou 2022-03-21 12:17:03 +03:00 committed by GitHub
parent 4b29f2e001
commit db5360fb89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 627 additions and 48 deletions

View File

@ -1,6 +1,8 @@
package cmd
import (
"os"
"github.com/SAP/jenkins-library/pkg/command"
"github.com/SAP/jenkins-library/pkg/gradle"
"github.com/SAP/jenkins-library/pkg/log"
@ -11,6 +13,8 @@ import (
type gradleExecuteBuildUtils interface {
command.ExecRunner
FileExists(filename string) (bool, error)
FileWrite(path string, content []byte, perm os.FileMode) error
FileRemove(path string) error
}
type gradleExecuteBuildUtilsBundle struct {
@ -30,18 +34,20 @@ func newGradleExecuteBuildUtils() gradleExecuteBuildUtils {
func gradleExecuteBuild(config gradleExecuteBuildOptions, telemetryData *telemetry.CustomData) {
utils := newGradleExecuteBuildUtils()
fileUtils := &piperutils.Files{}
err := runGradleExecuteBuild(&config, telemetryData, utils, fileUtils)
err := runGradleExecuteBuild(&config, telemetryData, utils)
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed: %w", err)
}
}
func runGradleExecuteBuild(config *gradleExecuteBuildOptions, telemetryData *telemetry.CustomData, utils gradleExecuteBuildUtils, fileUtils piperutils.FileUtils) error {
opt := &gradle.ExecuteOptions{BuildGradlePath: config.Path, Task: config.Task}
func runGradleExecuteBuild(config *gradleExecuteBuildOptions, telemetryData *telemetry.CustomData, utils gradleExecuteBuildUtils) error {
opt := &gradle.ExecuteOptions{
BuildGradlePath: config.Path,
Task: config.Task,
CreateBOM: config.CreateBOM,
}
_, err := gradle.Execute(opt, utils, fileUtils)
if err != nil {
if err := gradle.Execute(opt, utils); err != nil {
log.Entry().WithError(err).Errorln("build.gradle execution was failed: %w", err)
return err
}

View File

@ -5,19 +5,60 @@ package cmd
import (
"fmt"
"os"
"reflect"
"strings"
"time"
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/gcs"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/splunk"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/SAP/jenkins-library/pkg/validation"
"github.com/bmatcuk/doublestar"
"github.com/spf13/cobra"
)
type gradleExecuteBuildOptions struct {
Path string `json:"path,omitempty"`
Task string `json:"task,omitempty"`
CreateBOM bool `json:"createBOM,omitempty"`
}
type gradleExecuteBuildReports struct {
}
func (p *gradleExecuteBuildReports) persist(stepConfig gradleExecuteBuildOptions, gcpJsonKeyFilePath string, gcsBucketId string, gcsFolderPath string, gcsSubFolder string) {
if gcsBucketId == "" {
log.Entry().Info("persisting reports to GCS is disabled, because gcsBucketId is empty")
return
}
log.Entry().Info("Uploading reports to Google Cloud Storage...")
content := []gcs.ReportOutputParam{
{FilePattern: "**/bom.xml", ParamRef: "", StepResultType: "sbom"},
}
envVars := []gcs.EnvVar{
{Name: "GOOGLE_APPLICATION_CREDENTIALS", Value: gcpJsonKeyFilePath, Modified: false},
}
gcsClient, err := gcs.NewClient(gcs.WithEnvVars(envVars))
if err != nil {
log.Entry().Errorf("creation of GCS client failed: %v", err)
return
}
defer gcsClient.Close()
structVal := reflect.ValueOf(&stepConfig).Elem()
inputParameters := map[string]string{}
for i := 0; i < structVal.NumField(); i++ {
field := structVal.Type().Field(i)
if field.Type.String() == "string" {
paramName := strings.Split(field.Tag.Get("json"), ",")
paramValue, _ := structVal.Field(i).Interface().(string)
inputParameters[paramName[0]] = paramValue
}
}
if err := gcs.PersistReportsToGCS(gcsClient, content, inputParameters, gcsFolderPath, gcsBucketId, gcsSubFolder, doublestar.Glob, os.Stat); err != nil {
log.Entry().Errorf("failed to persist reports: %v", err)
}
}
// GradleExecuteBuildCommand This step runs a gradle build command with parameters provided to the step.
@ -27,6 +68,7 @@ func GradleExecuteBuildCommand() *cobra.Command {
metadata := gradleExecuteBuildMetadata()
var stepConfig gradleExecuteBuildOptions
var startTime time.Time
var reports gradleExecuteBuildReports
var logCollector *log.CollectorHook
var splunkClient *splunk.Splunk
telemetryClient := &telemetry.Telemetry{}
@ -78,6 +120,7 @@ func GradleExecuteBuildCommand() *cobra.Command {
stepTelemetryData := telemetry.CustomData{}
stepTelemetryData.ErrorCode = "1"
handler := func() {
reports.persist(stepConfig, GeneralConfig.GCPJsonKeyFilePath, GeneralConfig.GCSBucketId, GeneralConfig.GCSFolderPath, GeneralConfig.GCSSubFolder)
config.RemoveVaultSecretFiles()
stepTelemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds())
stepTelemetryData.ErrorCategory = log.GetErrorCategory().String()
@ -109,8 +152,9 @@ func GradleExecuteBuildCommand() *cobra.Command {
}
func addGradleExecuteBuildFlags(cmd *cobra.Command, stepConfig *gradleExecuteBuildOptions) {
cmd.Flags().StringVar(&stepConfig.Path, "path", os.Getenv("PIPER_path"), "Path to the folder with gradle.build file which should be executed.")
cmd.Flags().StringVar(&stepConfig.Path, "path", os.Getenv("PIPER_path"), "Path to the folder with build.gradle (or build.gradle.kts) file which should be executed.")
cmd.Flags().StringVar(&stepConfig.Task, "task", `build`, "Gradle task that should be executed.")
cmd.Flags().BoolVar(&stepConfig.CreateBOM, "createBOM", false, "Creates the bill of materials (BOM) using CycloneDX plugin.")
}
@ -143,11 +187,31 @@ func gradleExecuteBuildMetadata() config.StepData {
Aliases: []config.Alias{},
Default: `build`,
},
{
Name: "createBOM",
ResourceRef: []config.ResourceReference{},
Scope: []string{"GENERAL", "STEPS", "STAGES", "PARAMETERS"},
Type: "bool",
Mandatory: false,
Aliases: []config.Alias{},
Default: false,
},
},
},
Containers: []config.Container{
{Name: "gradle", Image: "gradle:6-jdk11-alpine"},
},
Outputs: config.StepOutputs{
Resources: []config.StepResources{
{
Name: "reports",
Type: "reports",
Parameters: []map[string]interface{}{
{"filePattern": "**/bom.xml", "type": "sbom"},
},
},
},
},
},
}
return theMetaData

View File

@ -14,14 +14,12 @@ type gradleExecuteBuildMockUtils struct {
*mock.FilesMock
}
type gradleExecuteBuildFileMock struct {
*mock.FilesMock
fileReadContent map[string]string
fileReadErr map[string]error
func (f gradleExecuteBuildMockUtils) DirExists(path string) (bool, error) {
return strings.EqualFold(path, "path/to/"), nil
}
func (f *gradleExecuteBuildFileMock) DirExists(path string) (bool, error) {
return strings.EqualFold(path, "path/to/"), nil
func (f gradleExecuteBuildMockUtils) FileExists(filePath string) (bool, error) {
return strings.EqualFold(filePath, "path/to/build.gradle"), nil
}
func newGradleExecuteBuildTestsUtils() gradleExecuteBuildMockUtils {
@ -40,10 +38,8 @@ func TestRunGradleExecuteBuild(t *testing.T) {
}
u := newShellExecuteTestsUtils()
m := &gradleExecuteBuildFileMock{}
err := runGradleExecuteBuild(options, nil, u, m)
assert.EqualError(t, err, "the specified gradle script could not be found")
err := runGradleExecuteBuild(options, nil, u)
assert.EqualError(t, err, "the specified gradle build script could not be found")
})
t.Run("success case - build.gradle is present", func(t *testing.T) {
@ -52,9 +48,8 @@ func TestRunGradleExecuteBuild(t *testing.T) {
}
u := newGradleExecuteBuildTestsUtils()
m := &gradleExecuteBuildFileMock{}
err := runGradleExecuteBuild(o, nil, u, m)
err := runGradleExecuteBuild(o, nil, u)
assert.NoError(t, err)
})

View File

@ -0,0 +1,162 @@
//go:build integration
// +build integration
// can be execute with go test -tags=integration ./integration/...
package main
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/testcontainers/testcontainers-go"
)
func TestGradleExecuteBuild_JavaProject_BOMCreation(t *testing.T) {
ctx := context.Background()
pwd, err := os.Getwd()
assert.NoError(t, err, "Getting current working directory failed.")
pwd = filepath.Dir(pwd)
// using custom createTmpDir function to avoid issues with symlinks on Docker for Mac
tempDir, err := createTmpDir("")
defer os.RemoveAll(tempDir) // clean up
assert.NoError(t, err, "Error when creating temp dir")
err = copyDir(filepath.Join(pwd, "integration", "testdata", "TestGradleIntegration", "java-project"), tempDir)
if err != nil {
t.Fatal("Failed to copy test project.")
}
//workaround to use test script util it is possible to set workdir for Exec call
testScript := fmt.Sprintf(`#!/bin/sh
cd /test
/piperbin/piper gradleExecuteBuild --createBOM >test-log.txt 2>&1
`)
ioutil.WriteFile(filepath.Join(tempDir, "runPiper.sh"), []byte(testScript), 0700)
reqNode := testcontainers.ContainerRequest{
Image: "gradle:6-jdk11-alpine",
Cmd: []string{"tail", "-f"},
BindMounts: map[string]string{
pwd: "/piperbin",
tempDir: "/test",
},
}
nodeContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: reqNode,
Started: true,
})
code, err := nodeContainer.Exec(ctx, []string{"sh", "/test/runPiper.sh"})
assert.NoError(t, err)
assert.Equal(t, 0, code)
content, err := ioutil.ReadFile(filepath.Join(tempDir, "/test-log.txt"))
if err != nil {
t.Fatal("Could not read test-log.txt.", err)
}
output := string(content)
assert.Contains(t, output, "info gradleExecuteBuild - running command: gradle tasks")
assert.Contains(t, output, "info gradleExecuteBuild - running command: gradle --init-script cyclonedx.gradle cyclonedxBom")
assert.Contains(t, output, "info gradleExecuteBuild - running command: gradle build")
assert.Contains(t, output, "info gradleExecuteBuild - BUILD SUCCESSFUL")
assert.Contains(t, output, "info gradleExecuteBuild - SUCCESS")
//workaround to use test script util it is possible to set workdir for Exec call
testScript = fmt.Sprintf(`#!/bin/sh
cd /test
ls -l ./build/reports/ >files-list.txt 2>&1
`)
ioutil.WriteFile(filepath.Join(tempDir, "runPiper.sh"), []byte(testScript), 0700)
code, err = nodeContainer.Exec(ctx, []string{"sh", "/test/runPiper.sh"})
assert.NoError(t, err)
assert.Equal(t, 0, code)
content, err = ioutil.ReadFile(filepath.Join(tempDir, "/files-list.txt"))
if err != nil {
t.Fatal("Could not read files-list.txt.", err)
}
output = string(content)
assert.Contains(t, output, "bom.xml")
}
func TestGradleExecuteBuild_JavaProjectWithBomPlugin(t *testing.T) {
ctx := context.Background()
pwd, err := os.Getwd()
assert.NoError(t, err, "Getting current working directory failed.")
pwd = filepath.Dir(pwd)
// using custom createTmpDir function to avoid issues with symlinks on Docker for Mac
tempDir, err := createTmpDir("")
defer os.RemoveAll(tempDir) // clean up
assert.NoError(t, err, "Error when creating temp dir")
err = copyDir(filepath.Join(pwd, "integration", "testdata", "TestGradleIntegration", "java-project-with-bom-plugin"), tempDir)
if err != nil {
t.Fatal("Failed to copy test project.")
}
//workaround to use test script util it is possible to set workdir for Exec call
testScript := fmt.Sprintf(`#!/bin/sh
cd /test
/piperbin/piper gradleExecuteBuild >test-log.txt 2>&1
`)
ioutil.WriteFile(filepath.Join(tempDir, "runPiper.sh"), []byte(testScript), 0700)
reqNode := testcontainers.ContainerRequest{
Image: "gradle:6-jdk11-alpine",
Cmd: []string{"tail", "-f"},
BindMounts: map[string]string{
pwd: "/piperbin",
tempDir: "/test",
},
}
nodeContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: reqNode,
Started: true,
})
code, err := nodeContainer.Exec(ctx, []string{"sh", "/test/runPiper.sh"})
assert.NoError(t, err)
assert.Equal(t, 0, code)
content, err := ioutil.ReadFile(filepath.Join(tempDir, "/test-log.txt"))
if err != nil {
t.Fatal("Could not read test-log.txt.", err)
}
output := string(content)
assert.Contains(t, output, "info gradleExecuteBuild - running command: gradle tasks")
assert.Contains(t, output, "gradle cyclonedxBom")
assert.Contains(t, output, "info gradleExecuteBuild - running command: gradle build")
assert.Contains(t, output, "info gradleExecuteBuild - BUILD SUCCESSFUL")
assert.Contains(t, output, "info gradleExecuteBuild - SUCCESS")
//workaround to use test script util it is possible to set workdir for Exec call
testScript = fmt.Sprintf(`#!/bin/sh
cd /test
ls -l ./build/reports/ >files-list.txt 2>&1
`)
ioutil.WriteFile(filepath.Join(tempDir, "runPiper.sh"), []byte(testScript), 0700)
code, err = nodeContainer.Exec(ctx, []string{"sh", "/test/runPiper.sh"})
assert.NoError(t, err)
assert.Equal(t, 0, code)
content, err = ioutil.ReadFile(filepath.Join(tempDir, "/files-list.txt"))
if err != nil {
t.Fatal("Could not read files-list.txt.", err)
}
output = string(content)
assert.Contains(t, output, "bom.xml")
}

View File

@ -0,0 +1,37 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/

View File

@ -0,0 +1,2 @@
general:
createBOM: true

View File

@ -0,0 +1,27 @@
plugins {
id 'org.springframework.boot' version '2.6.5-SNAPSHOT'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
id "org.cyclonedx.bom" version "1.5.0"
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://repo.spring.io/snapshot' }
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.projectreactor:reactor-test'
}
tasks.named('test') {
useJUnitPlatform()
}

View File

@ -0,0 +1,8 @@
pluginManagement {
repositories {
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://repo.spring.io/snapshot' }
gradlePluginPortal()
}
}
rootProject.name = 'demo'

View File

@ -0,0 +1,13 @@
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

View File

@ -0,0 +1,13 @@
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class DemoApplicationTests {
@Test
void contextLoads() {
}
}

View File

@ -0,0 +1,37 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/

View File

@ -0,0 +1,26 @@
plugins {
id 'org.springframework.boot' version '2.6.5-SNAPSHOT'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://repo.spring.io/snapshot' }
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.projectreactor:reactor-test'
}
tasks.named('test') {
useJUnitPlatform()
}

View File

@ -0,0 +1,8 @@
pluginManagement {
repositories {
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://repo.spring.io/snapshot' }
gradlePluginPortal()
}
}
rootProject.name = 'demo'

View File

@ -0,0 +1,13 @@
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

View File

@ -0,0 +1,13 @@
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class DemoApplicationTests {
@Test
void contextLoads() {
}
}

View File

@ -4,36 +4,76 @@ import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
)
const exec = "gradle"
const (
exec = "gradle"
bomTaskName = "cyclonedxBom"
groovyBuildScriptName = "build.gradle"
kotlinBuildScriptName = "build.gradle.kts"
initScriptName = "cyclonedx.gradle"
)
const initScriptContent = `
initscript {
repositories {
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "com.cyclonedx:cyclonedx-gradle-plugin:1.5.0"
}
}
rootProject {
apply plugin: 'java'
apply plugin: 'maven'
apply plugin: org.cyclonedx.gradle.CycloneDxPlugin
}
`
type Utils interface {
Stdout(out io.Writer)
Stderr(err io.Writer)
RunExecutable(e string, p ...string) error
FileExists(filename string) (bool, error)
FileWrite(path string, content []byte, perm os.FileMode) error
FileRemove(path string) error
}
// ExecuteOptions are used by Execute() to construct the Gradle command line.
type ExecuteOptions struct {
BuildGradlePath string `json:"path,omitempty"`
Task string `json:"task,omitempty"`
ReturnStdout bool `json:"returnStdout,omitempty"`
CreateBOM bool `json:"createBOM,omitempty"`
}
func Execute(options *ExecuteOptions, utils Utils, fileUtils piperutils.FileUtils) (string, error) {
exists, err := fileUtils.DirExists(options.BuildGradlePath)
if !exists {
return "", fmt.Errorf("the specified gradle script could not be found")
func Execute(options *ExecuteOptions, utils Utils) error {
groovyBuildScriptExists, err := utils.FileExists(filepath.Join(options.BuildGradlePath, groovyBuildScriptName))
if err != nil {
return fmt.Errorf("failed to check if file exists: %w", err)
}
kotlinBuildScriptExists, err := utils.FileExists(filepath.Join(options.BuildGradlePath, kotlinBuildScriptName))
if err != nil {
return fmt.Errorf("failed to check if file exists: %w", err)
}
if !groovyBuildScriptExists && !kotlinBuildScriptExists {
return fmt.Errorf("the specified gradle build script could not be found")
}
stdOutBuf, stdOut := evaluateStdOut(options)
utils.Stdout(stdOut)
utils.Stderr(log.Writer())
if options.CreateBOM {
if err := createBOM(options, utils); err != nil {
return fmt.Errorf("failed to create BOM: %w", err)
}
}
parameters := getParametersFromOptions(options)
@ -41,23 +81,10 @@ func Execute(options *ExecuteOptions, utils Utils, fileUtils piperutils.FileUtil
if err != nil {
log.SetErrorCategory(log.ErrorBuild)
commandLine := append([]string{exec}, parameters...)
return "", fmt.Errorf("failed to run executable, command: '%s', error: %w", commandLine, err)
return fmt.Errorf("failed to run executable, command: '%s', error: %w", commandLine, err)
}
if stdOutBuf == nil {
return "", nil
}
return string(stdOutBuf.Bytes()), nil
}
func evaluateStdOut(options *ExecuteOptions) (*bytes.Buffer, io.Writer) {
var stdOutBuf *bytes.Buffer
stdOut := log.Writer()
if options.ReturnStdout {
stdOutBuf = new(bytes.Buffer)
stdOut = io.MultiWriter(stdOut, stdOutBuf)
}
return stdOutBuf, stdOut
return nil
}
func getParametersFromOptions(options *ExecuteOptions) []string {
@ -74,3 +101,31 @@ func getParametersFromOptions(options *ExecuteOptions) []string {
return parameters
}
// CreateBOM generates BOM file using CycloneDX
func createBOM(options *ExecuteOptions, utils Utils) error {
// check if gradle task cyclonedxBom exists
stdOutBuf := new(bytes.Buffer)
stdOut := log.Writer()
stdOut = io.MultiWriter(stdOut, stdOutBuf)
utils.Stdout(stdOut)
if err := utils.RunExecutable(exec, "tasks"); err != nil {
return fmt.Errorf("failed list gradle tasks: %w", err)
}
if strings.Contains(stdOutBuf.String(), bomTaskName) {
if err := utils.RunExecutable(exec, bomTaskName); err != nil {
return fmt.Errorf("BOM creation failed: %w", err)
}
} else {
err := utils.FileWrite(filepath.Join(options.BuildGradlePath, initScriptName), []byte(initScriptContent), 0644)
if err != nil {
return fmt.Errorf("failed create init script: %w", err)
}
defer utils.FileRemove(filepath.Join(options.BuildGradlePath, initScriptName))
if err := utils.RunExecutable(exec, "--init-script", filepath.Join(options.BuildGradlePath, initScriptName), bomTaskName); err != nil {
return fmt.Errorf("BOM creation failed: %w", err)
}
}
return nil
}

83
pkg/gradle/gradle_test.go Normal file
View File

@ -0,0 +1,83 @@
package gradle
import (
"os"
"github.com/SAP/jenkins-library/pkg/mock"
"testing"
"github.com/stretchr/testify/assert"
)
type MockUtils struct {
writtenFiles []string
removedFiles []string
*mock.FilesMock
*mock.ExecMockRunner
}
func NewMockUtils(downloadShouldFail bool) *MockUtils {
utils := MockUtils{
FilesMock: &mock.FilesMock{},
ExecMockRunner: &mock.ExecMockRunner{},
}
return &utils
}
func (f *MockUtils) FileExists(filePath string) (bool, error) {
switch filePath {
case "build.gradle":
return true, nil
case "path/to/build.gradle":
return true, nil
}
return false, nil
}
func (f *MockUtils) FileWrite(path string, content []byte, perm os.FileMode) error {
f.writtenFiles = append(f.writtenFiles, path)
return nil
}
func (f *MockUtils) FileRemove(path string) error {
f.removedFiles = append(f.removedFiles, path)
return nil
}
func TestExecute(t *testing.T) {
t.Run("success - gradle build", func(t *testing.T) {
utils := NewMockUtils(false)
opts := ExecuteOptions{
BuildGradlePath: "path/to",
Task: "build",
CreateBOM: false,
}
err := Execute(&opts, utils)
assert.NoError(t, err)
assert.Equal(t, 1, len(utils.Calls))
assert.Equal(t, mock.ExecCall{Exec: "gradle", Params: []string{"build", "-p", "path/to"}}, utils.Calls[0])
assert.Equal(t, []string(nil), utils.writtenFiles)
assert.Equal(t, []string(nil), utils.removedFiles)
})
t.Run("success - bom creation", func(t *testing.T) {
utils := NewMockUtils(false)
opts := ExecuteOptions{
Task: "build",
CreateBOM: true,
}
err := Execute(&opts, utils)
assert.NoError(t, err)
assert.Equal(t, 3, len(utils.Calls))
assert.Equal(t, mock.ExecCall{Exec: "gradle", Params: []string{"tasks"}}, utils.Calls[0])
assert.Equal(t, mock.ExecCall{Exec: "gradle", Params: []string{"--init-script", "cyclonedx.gradle", "cyclonedxBom"}}, utils.Calls[1])
assert.Equal(t, mock.ExecCall{Exec: "gradle", Params: []string{"build"}}, utils.Calls[2])
assert.Equal(t, []string{"cyclonedx.gradle"}, utils.writtenFiles)
assert.Equal(t, []string{"cyclonedx.gradle"}, utils.removedFiles)
})
}

View File

@ -10,7 +10,7 @@ spec:
- name: buildGradlePath
deprecated: false
type: string
description: Path to the folder with gradle.build file which should be executed.
description: Path to the folder with build.gradle (or build.gradle.kts) file which should be executed.
scope:
- PARAMETERS
- STAGES
@ -25,6 +25,21 @@ spec:
- STEPS
mandatory: false
default: build
- name: createBOM
type: bool
description: Creates the bill of materials (BOM) using CycloneDX plugin.
scope:
- GENERAL
- STEPS
- STAGES
- PARAMETERS
outputs:
resources:
- name: reports
type: reports
params:
- filePattern: "**/bom.xml"
type: sbom
containers:
- name: gradle
image: gradle:6-jdk11-alpine