You've already forked sap-jenkins-library
mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-09-16 09:26:22 +02:00
feat(cpe): provide go templating functions (#3872)
* feat(cpe): provide go templating functions * change type * fix: type in test * chore: add comment for exported function * fix: ensure that custom returns string properly * fix types and add tests Co-authored-by: Anil Keshav <anil.keshav@sap.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -48,5 +48,4 @@ AUnitResults.html
|
||||
cmd/checkmarx/piper_checkmarx_report.json
|
||||
cmd/fortify/piper_fortify_report.html
|
||||
cmd/fortify/piper_fortify_report.json
|
||||
cmd/toolruns/toolrun_malwarescan_20220519143229.json
|
||||
cmd/toolruns/toolrun_protecode_20220519143230.json
|
||||
cmd/toolruns
|
||||
|
@@ -9,7 +9,6 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/buildsettings"
|
||||
"github.com/SAP/jenkins-library/pkg/certutils"
|
||||
@@ -174,11 +173,11 @@ func runGolangBuild(config *golangBuildOptions, telemetryData *telemetry.CustomD
|
||||
ldflags := ""
|
||||
|
||||
if len(config.LdflagsTemplate) > 0 {
|
||||
var err error
|
||||
ldflags, err = prepareLdflags(config, utils, GeneralConfig.EnvRootPath)
|
||||
ldf, err := prepareLdflags(config, utils, GeneralConfig.EnvRootPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ldflags = (*ldf).String()
|
||||
log.Entry().Infof("ldflags from template: '%v'", ldflags)
|
||||
}
|
||||
|
||||
@@ -405,7 +404,7 @@ func reportGolangTestCoverage(config *golangBuildOptions, utils golangBuildUtils
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepareLdflags(config *golangBuildOptions, utils golangBuildUtils, envRootPath string) (string, error) {
|
||||
func prepareLdflags(config *golangBuildOptions, utils golangBuildUtils, envRootPath string) (*bytes.Buffer, error) {
|
||||
cpe := piperenv.CPEMap{}
|
||||
err := cpe.LoadFromDisk(path.Join(envRootPath, "commonPipelineEnvironment"))
|
||||
if err != nil {
|
||||
@@ -413,23 +412,7 @@ func prepareLdflags(config *golangBuildOptions, utils golangBuildUtils, envRootP
|
||||
}
|
||||
|
||||
log.Entry().Debugf("ldflagsTemplate in use: %v", config.LdflagsTemplate)
|
||||
tmpl, err := template.New("ldflags").Parse(config.LdflagsTemplate)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse ldflagsTemplate '%v': %w", config.LdflagsTemplate, err)
|
||||
}
|
||||
|
||||
ldflagsParams := struct {
|
||||
CPE map[string]interface{}
|
||||
}{
|
||||
CPE: map[string]interface{}(cpe),
|
||||
}
|
||||
var generatedLdflags bytes.Buffer
|
||||
err = tmpl.Execute(&generatedLdflags, ldflagsParams)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to execute ldflagsTemplate '%v': %w", config.LdflagsTemplate, err)
|
||||
}
|
||||
|
||||
return generatedLdflags.String(), nil
|
||||
return cpe.ParseTemplate(config.LdflagsTemplate)
|
||||
}
|
||||
|
||||
func runGolangBuildPerArchitecture(config *golangBuildOptions, goModFile *modfile.File, utils golangBuildUtils, ldflags string, architecture multiarch.Platform) ([]string, error) {
|
||||
|
@@ -310,7 +310,7 @@ go 1.17`
|
||||
telemetryData := telemetry.CustomData{}
|
||||
|
||||
err := runGolangBuild(&config, &telemetryData, utils, &cpe)
|
||||
assert.Contains(t, fmt.Sprint(err), "failed to parse ldflagsTemplate")
|
||||
assert.Contains(t, fmt.Sprint(err), "failed to parse cpe template")
|
||||
})
|
||||
|
||||
t.Run("failure - build failure", func(t *testing.T) {
|
||||
@@ -626,14 +626,14 @@ func TestPrepareLdflags(t *testing.T) {
|
||||
utils := newGolangBuildTestsUtils()
|
||||
result, err := prepareLdflags(&config, utils, dir)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "-X version=1.2.3", result)
|
||||
assert.Equal(t, "-X version=1.2.3", (*result).String())
|
||||
})
|
||||
|
||||
t.Run("error - template parsing", func(t *testing.T) {
|
||||
config := golangBuildOptions{LdflagsTemplate: "-X version={{ .CPE.artifactVersion "}
|
||||
utils := newGolangBuildTestsUtils()
|
||||
_, err := prepareLdflags(&config, utils, dir)
|
||||
assert.Contains(t, fmt.Sprint(err), "failed to parse ldflagsTemplate")
|
||||
assert.Contains(t, fmt.Sprint(err), "failed to parse cpe template")
|
||||
})
|
||||
}
|
||||
|
||||
|
@@ -2,6 +2,7 @@ package piperenv
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -56,7 +57,10 @@ func writeToDisk(filename string, data []byte) error {
|
||||
|
||||
if _, err := os.Stat(filepath.Dir(filename)); os.IsNotExist(err) {
|
||||
log.Entry().Debugf("Creating directory: %v", filepath.Dir(filename))
|
||||
os.MkdirAll(filepath.Dir(filename), 0777)
|
||||
cErr := os.MkdirAll(filepath.Dir(filename), 0777)
|
||||
if cErr != nil {
|
||||
return fmt.Errorf("failed to create directory %v, %w", filepath.Dir(filename), cErr)
|
||||
}
|
||||
}
|
||||
|
||||
//ToDo: make sure to not overwrite file but rather add another file? Create error if already existing?
|
||||
|
87
pkg/piperenv/templating.go
Normal file
87
pkg/piperenv/templating.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package piperenv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// ParseTemplate allows to parse a template which contains references to the CPE
|
||||
// Utility functions make it simple to access specific parts of the CPE
|
||||
func (c *CPEMap) ParseTemplate(cpeTemplate string) (*bytes.Buffer, error) {
|
||||
funcMap := template.FuncMap{
|
||||
"cpe": c.cpe,
|
||||
"cpecustom": c.custom,
|
||||
"git": c.git,
|
||||
"imageDigest": c.imageDigest,
|
||||
"imageTag": c.imageTag,
|
||||
|
||||
// ToDo: add template function for artifacts
|
||||
// This requires alignment on artifact handling before, though
|
||||
}
|
||||
|
||||
tmpl, err := template.New("cpetemplate").Funcs(funcMap).Parse(cpeTemplate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse cpe template '%v': %w", cpeTemplate, err)
|
||||
}
|
||||
|
||||
tmplParams := struct {
|
||||
CPE map[string]interface{}
|
||||
}{
|
||||
CPE: map[string]interface{}(*c),
|
||||
}
|
||||
|
||||
var generated bytes.Buffer
|
||||
err = tmpl.Execute(&generated, tmplParams)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute cpe template '%v': %w", cpeTemplate, err)
|
||||
}
|
||||
|
||||
return &generated, nil
|
||||
}
|
||||
|
||||
func (c *CPEMap) cpe(element string) string {
|
||||
// ToDo: perform validity checks to allow only selected fields for now?
|
||||
// This would allow a stable contract and could perform conversions in case a contract changes.
|
||||
|
||||
return fmt.Sprint(map[string]interface{}(*c)[element])
|
||||
}
|
||||
|
||||
func (c *CPEMap) custom(element string) string {
|
||||
return fmt.Sprint(map[string]interface{}(*c)[fmt.Sprintf("custom/%v", element)])
|
||||
}
|
||||
|
||||
func (c *CPEMap) git(element string) string {
|
||||
var el string
|
||||
if element == "organization" || element == "repository" {
|
||||
el = fmt.Sprint(map[string]interface{}(*c)[fmt.Sprintf("github/%v", element)])
|
||||
} else {
|
||||
el = fmt.Sprint(map[string]interface{}(*c)[fmt.Sprintf("git/%v", element)])
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
func (c *CPEMap) imageDigest(imageName string) string {
|
||||
digests, _ := map[string]interface{}(*c)["container/imageDigests"].([]interface{})
|
||||
imageNames, _ := map[string]interface{}(*c)["container/imageNames"].([]interface{})
|
||||
if len(digests) > 0 && len(digests) == len(imageNames) {
|
||||
for i, image := range imageNames {
|
||||
if fmt.Sprint(image) == imageName {
|
||||
return fmt.Sprint(digests[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *CPEMap) imageTag(imageName string) string {
|
||||
nameTags, _ := map[string]interface{}(*c)["container/imageNameTags"].([]interface{})
|
||||
for _, nameTag := range nameTags {
|
||||
nt := strings.Split(fmt.Sprint(nameTag), ":")
|
||||
if nt[0] == imageName {
|
||||
return nt[1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
219
pkg/piperenv/templating_test.go
Normal file
219
pkg/piperenv/templating_test.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package piperenv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseTemplate(t *testing.T) {
|
||||
tt := []struct {
|
||||
template string
|
||||
cpe CPEMap
|
||||
expected string
|
||||
expectedError error
|
||||
}{
|
||||
{template: `version: {{index .CPE "artifactVersion"}}, sha: {{git "commitId"}}`, expected: "version: 1.2.3, sha: thisIsMyTestSha"},
|
||||
{template: "version: {{", expectedError: fmt.Errorf("failed to parse cpe template 'version: {{'")},
|
||||
}
|
||||
|
||||
cpe := CPEMap{
|
||||
"artifactVersion": "1.2.3",
|
||||
"git/commitId": "thisIsMyTestSha",
|
||||
}
|
||||
|
||||
for _, test := range tt {
|
||||
res, err := cpe.ParseTemplate(test.template)
|
||||
if test.expectedError != nil {
|
||||
assert.Contains(t, fmt.Sprint(err), fmt.Sprint(test.expectedError))
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.expected, (*res).String())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateFunctionCpe(t *testing.T) {
|
||||
t.Run("CPE from object", func(t *testing.T) {
|
||||
tt := []struct {
|
||||
element string
|
||||
expected string
|
||||
}{
|
||||
{element: "artifactVersion", expected: "1.2.3"},
|
||||
{element: "git/commitId", expected: "thisIsMyTestSha"},
|
||||
}
|
||||
|
||||
cpe := CPEMap{
|
||||
"artifactVersion": "1.2.3",
|
||||
"git/commitId": "thisIsMyTestSha",
|
||||
}
|
||||
|
||||
for _, test := range tt {
|
||||
assert.Equal(t, test.expected, cpe.cpe(test.element))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CPE from files", func(t *testing.T) {
|
||||
theVersion := "1.2.3"
|
||||
dir := t.TempDir()
|
||||
assert.NoError(t, ioutil.WriteFile(filepath.Join(dir, "artifactVersion"), []byte(theVersion), 0o666))
|
||||
cpe := CPEMap{}
|
||||
assert.NoError(t, cpe.LoadFromDisk(dir))
|
||||
|
||||
res, err := cpe.ParseTemplate(`{{cpe "artifactVersion"}}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, theVersion, (*res).String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestTemplateFunctionCustom(t *testing.T) {
|
||||
tt := []struct {
|
||||
element string
|
||||
expected string
|
||||
}{
|
||||
{element: "repositoryUrl", expected: "https://this.is.the.repo.url"},
|
||||
{element: "repositoryId", expected: "repoTestId"},
|
||||
}
|
||||
|
||||
cpe := CPEMap{
|
||||
"custom/repositoryUrl": "https://this.is.the.repo.url",
|
||||
"custom/repositoryId": "repoTestId",
|
||||
}
|
||||
|
||||
for _, test := range tt {
|
||||
assert.Equal(t, test.expected, cpe.custom(test.element))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateFunctionGit(t *testing.T) {
|
||||
tt := []struct {
|
||||
element string
|
||||
expected string
|
||||
}{
|
||||
{element: "commitId", expected: "thisIsMyTestSha"},
|
||||
{element: "repository", expected: "testRepo"},
|
||||
}
|
||||
|
||||
cpe := CPEMap{
|
||||
"git/commitId": "thisIsMyTestSha",
|
||||
"github/repository": "testRepo",
|
||||
}
|
||||
|
||||
for _, test := range tt {
|
||||
assert.Equal(t, test.expected, cpe.git(test.element))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateFunctionImageDigest(t *testing.T) {
|
||||
t.Run("CPE from object", func(t *testing.T) {
|
||||
tt := []struct {
|
||||
imageName string
|
||||
cpe CPEMap
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
imageName: "image1",
|
||||
cpe: CPEMap{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
imageName: "image2",
|
||||
cpe: CPEMap{
|
||||
"container/imageDigests": []interface{}{"digest1", "digest2", "digest3"},
|
||||
"container/imageNames": []interface{}{"image1", "image2", "image3"},
|
||||
},
|
||||
expected: "digest2",
|
||||
},
|
||||
{
|
||||
imageName: "image4",
|
||||
cpe: CPEMap{
|
||||
"container/imageDigests": []interface{}{"digest1", "digest2", "digest3"},
|
||||
"container/imageNames": []interface{}{"image1", "image2", "image3"},
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
imageName: "image1",
|
||||
cpe: CPEMap{
|
||||
"container/imageDigests": []interface{}{"digest1", "digest3"},
|
||||
"container/imageNames": []interface{}{"image1", "image2", "image3"},
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tt {
|
||||
assert.Equal(t, test.expected, test.cpe.imageDigest(test.imageName))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CPE from files", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
imageDigests := []string{"digest1", "digest2", "digest3"}
|
||||
imageNames := []string{"image1", "image2", "image3"}
|
||||
cpeOut := CPEMap{"container/imageDigests": imageDigests, "container/imageNames": imageNames}
|
||||
assert.NoError(t, cpeOut.WriteToDisk(dir))
|
||||
|
||||
cpe := CPEMap{}
|
||||
assert.NoError(t, cpe.LoadFromDisk(dir))
|
||||
|
||||
res, err := cpe.ParseTemplate(`{{imageDigest "image2"}}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "digest2", (*res).String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestTemplateFunctionImageTag(t *testing.T) {
|
||||
t.Run("CPE from object", func(t *testing.T) {
|
||||
tt := []struct {
|
||||
imageName string
|
||||
cpe CPEMap
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
imageName: "image1",
|
||||
cpe: CPEMap{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
imageName: "image2",
|
||||
cpe: CPEMap{
|
||||
"container/imageNameTags": []interface{}{"image1:tag1", "image2:tag2", "image3:tag3"},
|
||||
},
|
||||
expected: "tag2",
|
||||
},
|
||||
{
|
||||
imageName: "image4",
|
||||
cpe: CPEMap{
|
||||
"container/imageNameTags": []interface{}{"image1:tag1", "image2:tag2", "image3:tag3"},
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tt {
|
||||
assert.Equal(t, test.expected, test.cpe.imageTag(test.imageName))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CPE from files", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
imageNameTags := []string{"image1:tag1", "image2:tag2", "image3:tag3"}
|
||||
imageNames := []string{"image1", "image2", "image3"}
|
||||
cpeOut := CPEMap{"container/imageNameTags": imageNameTags, "container/imageNames": imageNames}
|
||||
assert.NoError(t, cpeOut.WriteToDisk(dir))
|
||||
|
||||
cpe := CPEMap{}
|
||||
assert.NoError(t, cpe.LoadFromDisk(dir))
|
||||
|
||||
res, err := cpe.ParseTemplate(`{{imageTag "image2"}}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "tag2", (*res).String())
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user