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:
parent
4b29f2e001
commit
db5360fb89
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
})
|
||||
|
||||
|
162
integration/integration_gradle_test.go
Normal file
162
integration/integration_gradle_test.go
Normal 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")
|
||||
}
|
37
integration/testdata/TestGradleIntegration/java-project-with-bom-plugin/.gitignore
vendored
Normal file
37
integration/testdata/TestGradleIntegration/java-project-with-bom-plugin/.gitignore
vendored
Normal 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/
|
2
integration/testdata/TestGradleIntegration/java-project-with-bom-plugin/.pipeline/config.yml
vendored
Normal file
2
integration/testdata/TestGradleIntegration/java-project-with-bom-plugin/.pipeline/config.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
general:
|
||||
createBOM: true
|
27
integration/testdata/TestGradleIntegration/java-project-with-bom-plugin/build.gradle
vendored
Normal file
27
integration/testdata/TestGradleIntegration/java-project-with-bom-plugin/build.gradle
vendored
Normal 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()
|
||||
}
|
8
integration/testdata/TestGradleIntegration/java-project-with-bom-plugin/settings.gradle
vendored
Normal file
8
integration/testdata/TestGradleIntegration/java-project-with-bom-plugin/settings.gradle
vendored
Normal 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'
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1 @@
|
||||
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
37
integration/testdata/TestGradleIntegration/java-project/.gitignore
vendored
Normal file
37
integration/testdata/TestGradleIntegration/java-project/.gitignore
vendored
Normal 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/
|
26
integration/testdata/TestGradleIntegration/java-project/build.gradle
vendored
Normal file
26
integration/testdata/TestGradleIntegration/java-project/build.gradle
vendored
Normal 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()
|
||||
}
|
8
integration/testdata/TestGradleIntegration/java-project/settings.gradle
vendored
Normal file
8
integration/testdata/TestGradleIntegration/java-project/settings.gradle
vendored
Normal 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'
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1 @@
|
||||
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
@ -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
83
pkg/gradle/gradle_test.go
Normal 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)
|
||||
})
|
||||
}
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user