1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-05-13 22:07:10 +02:00

feat(docker): use crane for pulling docker images (#3652)

This commit is contained in:
Christian Volk 2022-03-23 10:02:00 +01:00 committed by GitHub
parent f06890a9b2
commit 22f6aa156f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 506 additions and 707 deletions

View File

@ -4,7 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"regexp"
piperDocker "github.com/SAP/jenkins-library/pkg/docker"
"github.com/SAP/jenkins-library/pkg/log"
@ -17,11 +17,13 @@ import (
func containerSaveImage(config containerSaveImageOptions, telemetryData *telemetry.CustomData) {
var cachePath = "./cache"
dClientOptions := piperDocker.ClientOptions{ImageName: config.ContainerImage, RegistryURL: config.ContainerRegistryURL, LocalPath: config.FilePath, IncludeLayers: config.IncludeLayers}
fileUtils := piperutils.Files{}
dClientOptions := piperDocker.ClientOptions{ImageName: config.ContainerImage, RegistryURL: config.ContainerRegistryURL, LocalPath: config.FilePath}
dClient := &piperDocker.Client{}
dClient.SetOptions(dClientOptions)
_, err := runContainerSaveImage(&config, telemetryData, cachePath, "", dClient, piperutils.Files{})
_, err := runContainerSaveImage(&config, telemetryData, cachePath, "", dClient, fileUtils)
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
@ -32,55 +34,26 @@ func runContainerSaveImage(config *containerSaveImageOptions, telemetryData *tel
return "", err
}
err := os.RemoveAll(cachePath)
if err != nil {
return "", errors.Wrap(err, "failed to prepare cache")
}
err = os.Mkdir(cachePath, 0755)
if err != nil {
return "", errors.Wrap(err, "failed to create cache")
}
// ensure that download cache is cleaned up at the end
defer os.RemoveAll(cachePath)
imageSource, err := dClient.GetImageSource()
if err != nil {
return "", errors.Wrap(err, "failed to get docker image source")
}
image, err := dClient.DownloadImageToPath(imageSource, cachePath)
if err != nil {
return "", errors.Wrap(err, "failed to download docker image")
}
tarfilePath := config.FilePath
if len(tarfilePath) == 0 {
tarfilePath = filenameFromContainer(rootPath, config.ContainerImage)
} else {
tarfilePath = filenameFromContainer(rootPath, tarfilePath)
tarfilePath = filepath.Join(rootPath, tarfilePath)
}
tarFile, err := os.Create(tarfilePath)
if err != nil {
return "", errors.Wrapf(err, "failed to create %v for docker image", tarfilePath)
}
defer tarFile.Close()
if err := os.Chmod(tarfilePath, 0644); err != nil {
return "", errors.Wrapf(err, "failed to adapt permissions on %v", tarfilePath)
}
err = dClient.TarImage(tarFile, image)
if err != nil {
return "", errors.Wrap(err, "failed to tar container image")
log.Entry().Infof("Downloading '%s' to '%s'", config.ContainerImage, tarfilePath)
if _, err := dClient.DownloadImage(config.ContainerImage, tarfilePath); err != nil {
return "", errors.Wrap(err, "failed to download docker image")
}
return tarfilePath, nil
}
func filenameFromContainer(rootPath, containerImage string) string {
return filepath.Join(rootPath, strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(containerImage, "/", "_"), ":", "_"), ".", "_")+".tar")
re := regexp.MustCompile("[^a-zA-Z0-9-]")
return filepath.Join(rootPath, fmt.Sprintf("%s.tar", re.ReplaceAllString(containerImage, "_")))
}
func correctContainerDockerConfigEnvVar(config *containerSaveImageOptions, utils piperutils.FileUtils) error {

View File

@ -21,7 +21,6 @@ type containerSaveImageOptions struct {
ContainerRegistryPassword string `json:"containerRegistryPassword,omitempty"`
ContainerRegistryUser string `json:"containerRegistryUser,omitempty"`
FilePath string `json:"filePath,omitempty"`
IncludeLayers bool `json:"includeLayers,omitempty"`
DockerConfigJSON string `json:"dockerConfigJSON,omitempty"`
}
@ -119,12 +118,11 @@ It can be used no matter if a Docker daemon is available or not. It will also wo
}
func addContainerSaveImageFlags(cmd *cobra.Command, stepConfig *containerSaveImageOptions) {
cmd.Flags().StringVar(&stepConfig.ContainerRegistryURL, "containerRegistryUrl", os.Getenv("PIPER_containerRegistryUrl"), "The reference to the container registry where the image is located.")
cmd.Flags().StringVar(&stepConfig.ContainerRegistryURL, "containerRegistryUrl", os.Getenv("PIPER_containerRegistryUrl"), "For `buildTool: docker`: Url of the container registry - typically provided by the CI/CD environment.")
cmd.Flags().StringVar(&stepConfig.ContainerImage, "containerImage", os.Getenv("PIPER_containerImage"), "Container image to be saved.")
cmd.Flags().StringVar(&stepConfig.ContainerRegistryPassword, "containerRegistryPassword", os.Getenv("PIPER_containerRegistryPassword"), "For `buildTool: docker`: Password for container registry access - typically provided by the CI/CD environment.")
cmd.Flags().StringVar(&stepConfig.ContainerRegistryUser, "containerRegistryUser", os.Getenv("PIPER_containerRegistryUser"), "For `buildTool: docker`: Username for container registry access - typically provided by the CI/CD environment.")
cmd.Flags().StringVar(&stepConfig.FilePath, "filePath", os.Getenv("PIPER_filePath"), "The path to the file to which the image should be saved. Defaults to `containerImage.tar`")
cmd.Flags().BoolVar(&stepConfig.IncludeLayers, "includeLayers", false, "Flag if the docker layers should be included")
cmd.Flags().StringVar(&stepConfig.FilePath, "filePath", os.Getenv("PIPER_filePath"), "The path to the file to which the image should be saved.")
cmd.Flags().StringVar(&stepConfig.DockerConfigJSON, "dockerConfigJSON", os.Getenv("PIPER_dockerConfigJSON"), "Path to the file `.docker/config.json` - this is typically provided by your CI/CD system. You can find more details about the Docker credentials in the [Docker documentation](https://docs.docker.com/engine/reference/commandline/login/).")
cmd.MarkFlagRequired("containerRegistryUrl")
@ -220,15 +218,6 @@ func containerSaveImageMetadata() config.StepData {
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_filePath"),
},
{
Name: "includeLayers",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "bool",
Mandatory: false,
Aliases: []config.Alias{},
Default: false,
},
{
Name: "dockerConfigJSON",
ResourceRef: []config.ResourceReference{

View File

@ -2,106 +2,47 @@ package cmd
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"testing"
pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/stretchr/testify/assert"
"github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/fake"
)
type containerMock struct {
filePath string
imageSource string
registryURL string
localPath string
includeLayers bool
downloadImageErr string
imageSourceErr string
tarImageErr string
}
func (c *containerMock) DownloadImageToPath(imageSource, filePath string) (pkgutil.Image, error) {
c.imageSource = imageSource
c.filePath = filePath
if c.downloadImageErr != "" {
return pkgutil.Image{}, fmt.Errorf(c.downloadImageErr)
}
return pkgutil.Image{}, nil
}
func (c *containerMock) GetImageSource() (string, error) {
if c.imageSourceErr != "" {
return "", fmt.Errorf(c.imageSourceErr)
}
return "imageSource", nil
}
func (c *containerMock) TarImage(writer io.Writer, image pkgutil.Image) error {
if c.tarImageErr != "" {
return fmt.Errorf(c.tarImageErr)
}
writer.Write([]byte("This is a test"))
return nil
}
func TestRunContainerSaveImage(t *testing.T) {
telemetryData := telemetry.CustomData{}
t.Run("success case", func(t *testing.T) {
config := containerSaveImageOptions{}
tmpFolder, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal("failed to create temp dir")
}
defer os.RemoveAll(tmpFolder)
config.FilePath = "testfile.tar"
cacheFolder := filepath.Join(tmpFolder, "cache")
config.FilePath = "testfile"
dClient := containerMock{}
dClient := mock.DownloadMock{}
files := mock.FilesMock{}
filePath, err := runContainerSaveImage(&config, &telemetryData, cacheFolder, tmpFolder, &dClient, &files)
cacheFolder, err := files.TempDir("", "containerSaveImage-")
assert.NoError(t, err)
assert.Equal(t, cacheFolder, dClient.filePath)
assert.Equal(t, "imageSource", dClient.imageSource)
dClient.Stub = func(imgRef string, dest string) (v1.Image, error) {
files.AddFile(dest, []byte("This is a test"))
return &fake.FakeImage{}, nil
}
content, err := ioutil.ReadFile(filepath.Join(tmpFolder, "testfile.tar"))
filePath, err := runContainerSaveImage(&config, &telemetryData, cacheFolder, cacheFolder, &dClient, &files)
assert.NoError(t, err)
content, err := files.FileRead(filepath.Join(cacheFolder, "testfile.tar"))
assert.NoError(t, err)
assert.Equal(t, "This is a test", string(content))
assert.Contains(t, filePath, "testfile.tar")
})
t.Run("failure - cache creation", func(t *testing.T) {
config := containerSaveImageOptions{}
dClient := containerMock{}
files := mock.FilesMock{}
_, err := runContainerSaveImage(&config, &telemetryData, "", "", &dClient, &files)
assert.Contains(t, fmt.Sprint(err), "failed to create cache: mkdir :")
})
t.Run("failure - get image source", func(t *testing.T) {
config := containerSaveImageOptions{}
tmpFolder, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal("failed to create temp dir")
}
defer os.RemoveAll(tmpFolder)
dClient := containerMock{imageSourceErr: "image source error"}
files := mock.FilesMock{}
_, err = runContainerSaveImage(&config, &telemetryData, filepath.Join(tmpFolder, "cache"), tmpFolder, &dClient, &files)
assert.EqualError(t, err, "failed to get docker image source: image source error")
})
t.Run("failure - download image", func(t *testing.T) {
config := containerSaveImageOptions{}
tmpFolder, err := ioutil.TempDir("", "")
@ -110,25 +51,11 @@ func TestRunContainerSaveImage(t *testing.T) {
}
defer os.RemoveAll(tmpFolder)
dClient := containerMock{downloadImageErr: "download error"}
dClient := mock.DownloadMock{ReturnError: "download error"}
files := mock.FilesMock{}
_, err = runContainerSaveImage(&config, &telemetryData, filepath.Join(tmpFolder, "cache"), tmpFolder, &dClient, &files)
assert.EqualError(t, err, "failed to download docker image: download error")
})
t.Run("failure - tar image", func(t *testing.T) {
config := containerSaveImageOptions{}
tmpFolder, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal("failed to create temp dir")
}
defer os.RemoveAll(tmpFolder)
dClient := containerMock{tarImageErr: "tar error"}
files := mock.FilesMock{}
_, err = runContainerSaveImage(&config, &telemetryData, filepath.Join(tmpFolder, "cache"), tmpFolder, &dClient, &files)
assert.EqualError(t, err, "failed to tar container image: tar error")
})
}
func TestFilenameFromContainer(t *testing.T) {

View File

@ -5,7 +5,6 @@ import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"regexp"
"sort"
@ -29,25 +28,7 @@ import (
)
type detectUtils interface {
Abs(path string) (string, error)
FileExists(filename string) (bool, error)
FileRemove(filename string) error
Copy(src, dest string) (int64, error)
Move(src, dest string) error
DirExists(dest string) (bool, error)
FileRead(path string) ([]byte, error)
FileWrite(path string, content []byte, perm os.FileMode) error
MkdirAll(path string, perm os.FileMode) error
Chmod(path string, mode os.FileMode) error
Glob(pattern string) (matches []string, err error)
Chdir(path string) error
TempDir(string, string) (string, error)
RemoveAll(string) error
FileRename(string, string) error
Getwd() (string, error)
Symlink(oldname string, newname string) error
SHA256(path string) (string, error)
CurrentTime(format string) string
piperutils.FileUtils
GetExitCode() int
GetOsEnv() []string

View File

@ -150,10 +150,9 @@ func selectAndPrepareFileForMalwareScan(config *malwareExecuteScanOptions, utils
ContainerRegistryUser: config.ContainerRegistryUser,
ContainerRegistryPassword: config.ContainerRegistryPassword,
DockerConfigJSON: config.DockerConfigJSON,
IncludeLayers: config.ScanImageIncludeLayers,
FilePath: config.ScanImage,
}
dClientOptions := piperDocker.ClientOptions{ImageName: saveImageOptions.ContainerImage, RegistryURL: saveImageOptions.ContainerRegistryURL, LocalPath: "", IncludeLayers: saveImageOptions.IncludeLayers}
dClientOptions := piperDocker.ClientOptions{ImageName: saveImageOptions.ContainerImage, RegistryURL: saveImageOptions.ContainerRegistryURL, LocalPath: ""}
dClient := utils.newDockerClient(dClientOptions)
tarFile, err := runContainerSaveImage(&saveImageOptions, &telemetry.CustomData{}, "./cache", "", dClient, utils)

View File

@ -28,7 +28,6 @@ type malwareExecuteScanOptions struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
ScanImage string `json:"scanImage,omitempty"`
ScanImageIncludeLayers bool `json:"scanImageIncludeLayers,omitempty"`
ScanImageRegistryURL string `json:"scanImageRegistryUrl,omitempty"`
ScanFile string `json:"scanFile,omitempty"`
Timeout string `json:"timeout,omitempty"`
@ -176,7 +175,6 @@ func addMalwareExecuteScanFlags(cmd *cobra.Command, stepConfig *malwareExecuteSc
cmd.Flags().StringVar(&stepConfig.Username, "username", os.Getenv("PIPER_username"), "User")
cmd.Flags().StringVar(&stepConfig.Password, "password", os.Getenv("PIPER_password"), "Password")
cmd.Flags().StringVar(&stepConfig.ScanImage, "scanImage", os.Getenv("PIPER_scanImage"), "For `buildTool: docker`: Defines the docker image which should be scanned.")
cmd.Flags().BoolVar(&stepConfig.ScanImageIncludeLayers, "scanImageIncludeLayers", true, "For `buildTool: docker`: Defines if layers should be included.")
cmd.Flags().StringVar(&stepConfig.ScanImageRegistryURL, "scanImageRegistryUrl", os.Getenv("PIPER_scanImageRegistryUrl"), "For `buildTool: docker`: Defines the registry where the scanImage is located.")
cmd.Flags().StringVar(&stepConfig.ScanFile, "scanFile", os.Getenv("PIPER_scanFile"), "The file which is scanned for malware")
cmd.Flags().StringVar(&stepConfig.Timeout, "timeout", `600`, "timeout for http layer in seconds")
@ -344,15 +342,6 @@ func malwareExecuteScanMetadata() config.StepData {
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_scanImage"),
},
{
Name: "scanImageIncludeLayers",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "bool",
Mandatory: false,
Aliases: []config.Alias{},
Default: true,
},
{
Name: "scanImageRegistryUrl",
ResourceRef: []config.ResourceReference{

View File

@ -9,7 +9,9 @@ import (
"strings"
"testing"
pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util"
"github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/fake"
piperDocker "github.com/SAP/jenkins-library/pkg/docker"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/malwarescan"
@ -57,7 +59,7 @@ func (utils *malwareScanUtilsMockBundle) Scan(candidate io.Reader) (*malwarescan
}
func (utils *malwareScanUtilsMockBundle) newDockerClient(options piperDocker.ClientOptions) piperDocker.Download {
return &dockerClientMock{imageName: options.ImageName, registryURL: options.RegistryURL, localPath: options.LocalPath, includeLayers: options.IncludeLayers}
return &dockerClientMock{imageName: options.ImageName, registryURL: options.RegistryURL, localPath: options.LocalPath}
}
func TestMalwareScanWithNoBuildtool(t *testing.T) {
@ -287,16 +289,12 @@ type dockerClientMock struct {
includeLayers bool
}
func (c *dockerClientMock) GetImageSource() (string, error) {
return c.imageName, nil
//DownloadImage download the image to the specified path
func (c *dockerClientMock) DownloadImage(imageSource, filePath string) (v1.Image, error) {
return &fake.FakeImage{}, nil // fmt.Errorf("%s", filePath)
}
//DownloadImageToPath download the image to the specified path
func (c *dockerClientMock) DownloadImageToPath(imageSource, filePath string) (pkgutil.Image, error) {
return pkgutil.Image{}, nil // fmt.Errorf("%s", filePath)
}
//TarImage write a tar from the given image
func (c *dockerClientMock) TarImage(writer io.Writer, image pkgutil.Image) error {
return nil
//DownloadImage download the image to the specified path
func (c *dockerClientMock) DownloadImageContent(imageSource, filePath string) (v1.Image, error) {
return &fake.FakeImage{}, nil // fmt.Errorf("%s", filePath)
}

View File

@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
@ -15,11 +14,10 @@ import (
"github.com/pkg/errors"
"github.com/GoogleContainerTools/container-diff/pkg/util"
"github.com/SAP/jenkins-library/pkg/command"
piperDocker "github.com/SAP/jenkins-library/pkg/docker"
"github.com/SAP/jenkins-library/pkg/log"
StepResults "github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/protecode"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/SAP/jenkins-library/pkg/toolrecord"
@ -32,10 +30,15 @@ const (
stepResultFile = "protecodeExecuteScan.json"
)
var reportPath = "./"
var cachePath = "./cache"
var cacheProtecodeImagePath = "/protecode/Image"
var cacheProtecodePath = "/protecode"
type protecodeUtils interface {
piperutils.FileUtils
piperDocker.Download
}
type protecodeUtilsBundle struct {
*piperutils.Files
*piperDocker.Client
}
func protecodeExecuteScan(config protecodeExecuteScanOptions, telemetryData *telemetry.CustomData, influx *protecodeExecuteScanInflux) {
c := command.Command{}
@ -43,24 +46,40 @@ func protecodeExecuteScan(config protecodeExecuteScanOptions, telemetryData *tel
c.Stdout(log.Writer())
c.Stderr(log.Writer())
dClient := createDockerClient(&config)
//create client for sending api request
log.Entry().Debug("Create protecode client")
client := createProtecodeClient(&config)
dClientOptions := piperDocker.ClientOptions{ImageName: config.ScanImage, RegistryURL: config.DockerRegistryURL, LocalPath: config.FilePath}
dClient := &piperDocker.Client{}
dClient.SetOptions(dClientOptions)
utils := protecodeUtilsBundle{
Client: dClient,
Files: &piperutils.Files{},
}
influx.step_data.fields.protecode = false
if err := runProtecodeScan(&config, influx, dClient); err != nil {
if err := runProtecodeScan(&config, influx, client, utils, "./cache"); err != nil {
log.Entry().WithError(err).Fatal("Failed to execute protecode scan.")
}
influx.step_data.fields.protecode = true
}
func runProtecodeScan(config *protecodeExecuteScanOptions, influx *protecodeExecuteScanInflux, dClient piperDocker.Download) error {
func runProtecodeScan(config *protecodeExecuteScanOptions, influx *protecodeExecuteScanInflux, client protecode.Protecode, utils protecodeUtils, cachePath string) error {
// make sure cache exists
if err := utils.MkdirAll(cachePath, 0755); err != nil {
return err
}
correctDockerConfigEnvVar(config)
var fileName, filePath string
var err error
//create client for sending api request
log.Entry().Debug("Create protecode client")
client := createClient(config)
if len(config.FetchURL) == 0 && len(config.FilePath) == 0 {
log.Entry().Debugf("Get docker image: %v, %v, %v, %v", config.ScanImage, config.DockerRegistryURL, config.FilePath, config.IncludeLayers)
fileName, filePath, err = getDockerImage(dClient, config)
log.Entry().Debugf("Get docker image: %v, %v, %v", config.ScanImage, config.DockerRegistryURL, config.FilePath)
fileName, filePath, err = getDockerImage(utils, config, cachePath)
if err != nil {
return errors.Wrap(err, "failed to get Docker image")
}
@ -85,13 +104,13 @@ func runProtecodeScan(config *protecodeExecuteScanOptions, influx *protecodeExec
}
log.Entry().Debug("Execute protecode scan")
if err := executeProtecodeScan(influx, client, config, fileName, writeReportToFile); err != nil {
if err := executeProtecodeScan(influx, client, config, fileName, utils); err != nil {
return err
}
defer os.Remove(config.FilePath)
defer utils.FileRemove(config.FilePath)
if err := os.RemoveAll(filepath.Join(cachePath, cacheProtecodePath)); err != nil {
if err := utils.RemoveAll(cachePath); err != nil {
log.Entry().Warnf("Error during cleanup folder %v", err)
}
@ -109,55 +128,25 @@ func handleArtifactVersion(artifactVersion string) string {
return artifactVersion
}
func getDockerImage(dClient piperDocker.Download, config *protecodeExecuteScanOptions) (string, string, error) {
func getDockerImage(utils protecodeUtils, config *protecodeExecuteScanOptions, cachePath string) (string, string, error) {
m := regexp.MustCompile("[\\s@:/]")
cacheImagePath := filepath.Join(cachePath, cacheProtecodeImagePath)
deletePath := filepath.Join(cachePath, cacheProtecodePath)
err := os.RemoveAll(deletePath)
tarFileName := fmt.Sprintf("%s.tar", m.ReplaceAllString(config.ScanImage, "-"))
tarFilePath, err := filepath.Abs(filepath.Join(cachePath, tarFileName))
os.Mkdir(cacheImagePath, 600)
imageSource, err := dClient.GetImageSource()
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return "", "", errors.Wrap(err, "failed to get docker image")
return "", "", err
}
image, err := dClient.DownloadImageToPath(imageSource, cacheImagePath)
if err != nil {
if _, err = utils.DownloadImage(config.ScanImage, tarFilePath); err != nil {
return "", "", errors.Wrap(err, "failed to download docker image")
}
var fileName string
if util.IsTar(config.ScanImage) {
fileName = config.ScanImage
} else {
fileName = getTarName(config)
tarFilePath := filepath.Join(cachePath, fileName)
tarFile, err := os.Create(tarFilePath)
if err != nil {
log.SetErrorCategory(log.ErrorCustom)
return "", "", errors.Wrap(err, "failed to create tar for the docker image")
}
defer tarFile.Close()
if err := os.Chmod(tarFilePath, 0644); err != nil {
log.SetErrorCategory(log.ErrorCustom)
return "", "", errors.Wrap(err, "failed to set permissions on tar for the docker image")
}
if err = dClient.TarImage(tarFile, image); err != nil {
return "", "", errors.Wrap(err, "failed to tar the docker image")
}
}
resultFilePath := config.FilePath
if len(config.FilePath) <= 0 {
resultFilePath = cachePath
}
return fileName, resultFilePath, nil
return filepath.Base(tarFilePath), filepath.Dir(tarFilePath), nil
}
func executeProtecodeScan(influx *protecodeExecuteScanInflux, client protecode.Protecode, config *protecodeExecuteScanOptions, fileName string, writeReportToFile func(resp io.ReadCloser, reportFileName string) error) error {
func executeProtecodeScan(influx *protecodeExecuteScanInflux, client protecode.Protecode, config *protecodeExecuteScanOptions, fileName string, utils protecodeUtils) error {
reportPath := "./"
log.Entry().Debugf("[DEBUG] ===> Load existing product Group:%v, VerifyOnly:%v, Filename:%v, replaceProductId:%v", config.Group, config.VerifyOnly, fileName, config.ReplaceProductID)
@ -194,7 +183,7 @@ func executeProtecodeScan(influx *protecodeExecuteScanInflux, client protecode.P
}
// check if no existing is found
productID = uploadScanOrDeclareFetch(*config, productID, client, fileName)
productID = uploadScanOrDeclareFetch(utils, *config, productID, client, fileName)
log.Entry().Debugf("[DEBUG] ===> After 'uploadScanOrDeclareFetch' returned productID: %v", productID)
@ -207,7 +196,7 @@ func executeProtecodeScan(influx *protecodeExecuteScanInflux, client protecode.P
result := client.PollForResult(productID, config.TimeoutMinutes)
// write results to file
jsonData, _ := json.Marshal(result)
ioutil.WriteFile(filepath.Join(reportPath, scanResultFile), jsonData, 0644)
utils.FileWrite(filepath.Join(reportPath, scanResultFile), jsonData, 0644)
//check if result is ok else notify
if protecode.HasFailed(result) {
@ -218,10 +207,17 @@ func executeProtecodeScan(influx *protecodeExecuteScanInflux, client protecode.P
//loadReport
log.Entry().Debugf("Load report %v for %v", config.ReportFileName, productID)
resp := client.LoadReport(config.ReportFileName, productID)
//save report to filesystem
if err := writeReportToFile(*resp, config.ReportFileName); err != nil {
buf, err := io.ReadAll(*resp)
if err != nil {
return fmt.Errorf("unable to process protecode report %v", err)
}
if err = utils.FileWrite(config.ReportFileName, buf, 0644); err != nil {
log.Entry().Warningf("failed to write report: %s", err)
}
//clean scan from server
log.Entry().Debugf("Delete scan %v for %v", config.CleanupMode, productID)
client.DeleteScan(config.CleanupMode, productID)
@ -239,7 +235,7 @@ func executeProtecodeScan(influx *protecodeExecuteScanInflux, client protecode.P
Target: config.ReportFileName,
Vulnerabilities: vulns,
ProductID: fmt.Sprintf("%v", productID),
}, reportPath, stepResultFile, parsedResult, ioutil.WriteFile); err != nil {
}, reportPath, stepResultFile, parsedResult, utils); err != nil {
log.Entry().Warningf("failed to write report: %v", err)
}
@ -247,21 +243,21 @@ func executeProtecodeScan(influx *protecodeExecuteScanInflux, client protecode.P
setInfluxData(influx, parsedResult)
// write reports JSON
reports := []StepResults.Path{
reports := []piperutils.Path{
{Target: config.ReportFileName, Mandatory: true},
{Target: stepResultFile, Mandatory: true},
{Target: scanResultFile, Mandatory: true},
}
// write links JSON
webuiURL := fmt.Sprintf(webReportPath, config.ServerURL, productID)
links := []StepResults.Path{
links := []piperutils.Path{
{Name: "Protecode WebUI", Target: webuiURL},
{Name: "Protecode Report", Target: path.Join("artifact", config.ReportFileName), Scope: "job"},
}
// write custom report
scanReport := protecode.CreateCustomReport(fileName, productID, parsedResult, vulns)
paths, err := protecode.WriteCustomReports(scanReport, fileName, fmt.Sprint(productID))
paths, err := protecode.WriteCustomReports(scanReport, fileName, fmt.Sprint(productID), utils)
if err != nil {
// do not fail - consider failing later on
log.Entry().Warning("failed to create custom HTML/MarkDown file ...", err)
@ -275,10 +271,10 @@ func executeProtecodeScan(influx *protecodeExecuteScanInflux, client protecode.P
// do not fail until the framework is well established
log.Entry().Warning("TR_PROTECODE: Failed to create toolrecord file ...", err)
} else {
reports = append(reports, StepResults.Path{Target: toolRecordFileName})
reports = append(reports, piperutils.Path{Target: toolRecordFileName})
}
StepResults.PersistReportsAndLinks("protecodeExecuteScan", "", reports, links)
piperutils.PersistReportsAndLinks("protecodeExecuteScan", "", reports, links)
if config.FailOnSevereVulnerabilities && protecode.HasSevereVulnerabilities(result.Result, config.ExcludeCVEs) {
log.SetErrorCategory(log.ErrorCompliance)
@ -296,8 +292,7 @@ func setInfluxData(influx *protecodeExecuteScanInflux, result map[string]int) {
influx.protecode_data.fields.vulnerabilities = result["vulnerabilities"]
}
func createClient(config *protecodeExecuteScanOptions) protecode.Protecode {
func createProtecodeClient(config *protecodeExecuteScanOptions) protecode.Protecode {
var duration time.Duration = time.Duration(time.Minute * 1)
if len(config.TimeoutMinutes) > 0 {
@ -324,16 +319,7 @@ func createClient(config *protecodeExecuteScanOptions) protecode.Protecode {
return pc
}
func createDockerClient(config *protecodeExecuteScanOptions) piperDocker.Download {
dClientOptions := piperDocker.ClientOptions{ImageName: config.ScanImage, RegistryURL: config.DockerRegistryURL, LocalPath: config.FilePath, IncludeLayers: config.IncludeLayers}
dClient := &piperDocker.Client{}
dClient.SetOptions(dClientOptions)
return dClient
}
func uploadScanOrDeclareFetch(config protecodeExecuteScanOptions, productID int, client protecode.Protecode, fileName string) int {
func uploadScanOrDeclareFetch(utils protecodeUtils, config protecodeExecuteScanOptions, productID int, client protecode.Protecode, fileName string) int {
//check if the LoadExistingProduct) before returns an valid product id, than skip this
//if !hasExisting(productID, config.VerifyOnly) {
@ -343,7 +329,7 @@ func uploadScanOrDeclareFetch(config protecodeExecuteScanOptions, productID int,
if productID <= 0 {
log.Entry().Infof("New product creation started ... ")
// log.Entry().Debugf("[DEBUG] ===> New product creation started: %v", productID)
productID = uploadFile(config, productID, client, fileName, false)
productID = uploadFile(utils, config, productID, client, fileName, false)
log.Entry().Infof("New product has been successfully created: %v", productID)
// log.Entry().Debugf("[DEBUG] ===> After uploading [productID < 0] file returned productID: %v", productID)
@ -353,7 +339,7 @@ func uploadScanOrDeclareFetch(config protecodeExecuteScanOptions, productID int,
} else if (productID > 0) && !config.VerifyOnly {
log.Entry().Infof("Product already exists and 'VerifyOnly (reuseExisting)' is false then product (%v) binary and scan result will be replaced without creating a new product.", productID)
// log.Entry().Debugf("[DEBUG] ===> Replace binary entry point started %v", productID)
productID = uploadFile(config, productID, client, fileName, true)
productID = uploadFile(utils, config, productID, client, fileName, true)
// log.Entry().Debugf("[DEBUG] ===> After uploading file [(productID > 0) && !config.VerifyOnly] returned productID: %v", productID)
return productID
@ -366,7 +352,7 @@ func uploadScanOrDeclareFetch(config protecodeExecuteScanOptions, productID int,
}
}
func uploadFile(config protecodeExecuteScanOptions, productID int, client protecode.Protecode, fileName string, replaceBinary bool) int {
func uploadFile(utils protecodeUtils, config protecodeExecuteScanOptions, productID int, client protecode.Protecode, fileName string, replaceBinary bool) int {
// get calculated version for Version field
version := getProcessedVersion(&config)
@ -381,7 +367,7 @@ func uploadFile(config protecodeExecuteScanOptions, productID int, client protec
log.Entry().Fatalf("There is no file path configured for upload : %v", config.FilePath)
}
pathToFile := filepath.Join(config.FilePath, fileName)
if !(fileExists(pathToFile)) {
if exists, err := utils.FileExists(pathToFile); err != nil && !exists {
log.Entry().Fatalf("There is no file for upload: %v", pathToFile)
}
@ -397,14 +383,6 @@ func uploadFile(config protecodeExecuteScanOptions, productID int, client protec
return productID
}
func fileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
func hasExisting(productID int, verifyOnly bool) bool {
if (productID > 0) || verifyOnly {
return true
@ -412,17 +390,6 @@ func hasExisting(productID int, verifyOnly bool) bool {
return false
}
var writeReportToFile = func(resp io.ReadCloser, reportFileName string) error {
filePath := filepath.Join(reportPath, reportFileName)
f, err := os.Create(filePath)
if err == nil {
defer f.Close()
_, err = io.Copy(f, resp)
}
return err
}
func correctDockerConfigEnvVar(config *protecodeExecuteScanOptions) {
path := config.DockerConfigJSON
if len(path) > 0 {
@ -436,26 +403,6 @@ func correctDockerConfigEnvVar(config *protecodeExecuteScanOptions) {
}
}
func getTarName(config *protecodeExecuteScanOptions) string {
// remove original version
fileName := strings.TrimSuffix(config.ScanImage, ":"+config.Version)
// remove sha digest if exists
sha256 := "@sha256"
if index := strings.Index(fileName, sha256); index > -1 {
fileName = fileName[:index]
}
version := getProcessedVersion(config)
if len(version) > 0 {
fileName = fileName + "_" + version
}
fileName = strings.ReplaceAll(fileName, "/", "_")
return fileName + ".tar"
}
// Calculate version based on versioning model and artifact version or return custom scan version provided by user
func getProcessedVersion(config *protecodeExecuteScanOptions) string {
processedVersion := config.CustomScanVersion

View File

@ -29,7 +29,6 @@ type protecodeExecuteScanOptions struct {
DockerConfigJSON string `json:"dockerConfigJSON,omitempty"`
CleanupMode string `json:"cleanupMode,omitempty" validate:"possible-values=none binary complete"`
FilePath string `json:"filePath,omitempty"`
IncludeLayers bool `json:"includeLayers,omitempty"`
TimeoutMinutes string `json:"timeoutMinutes,omitempty"`
ServerURL string `json:"serverUrl,omitempty"`
ReportFileName string `json:"reportFileName,omitempty"`
@ -242,7 +241,6 @@ func addProtecodeExecuteScanFlags(cmd *cobra.Command, stepConfig *protecodeExecu
cmd.Flags().StringVar(&stepConfig.DockerConfigJSON, "dockerConfigJSON", os.Getenv("PIPER_dockerConfigJSON"), "Path to the file `.docker/config.json` - this is typically provided by your CI/CD system. You can find more details about the Docker credentials in the [Docker documentation](https://docs.docker.com/engine/reference/commandline/login/).")
cmd.Flags().StringVar(&stepConfig.CleanupMode, "cleanupMode", `binary`, "Decides which parts are removed from the Protecode backend after the scan")
cmd.Flags().StringVar(&stepConfig.FilePath, "filePath", os.Getenv("PIPER_filePath"), "The path to the file from local workspace to scan with Protecode")
cmd.Flags().BoolVar(&stepConfig.IncludeLayers, "includeLayers", false, "Flag if the docker layers should be included")
cmd.Flags().StringVar(&stepConfig.TimeoutMinutes, "timeoutMinutes", `60`, "The timeout to wait for the scan to finish")
cmd.Flags().StringVar(&stepConfig.ServerURL, "serverUrl", os.Getenv("PIPER_serverUrl"), "The URL to the Protecode backend")
cmd.Flags().StringVar(&stepConfig.ReportFileName, "reportFileName", `protecode_report.pdf`, "The file name of the report to be created")
@ -367,15 +365,6 @@ func protecodeExecuteScanMetadata() config.StepData {
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_filePath"),
},
{
Name: "includeLayers",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "bool",
Mandatory: false,
Aliases: []config.Alias{},
Default: false,
},
{
Name: "timeoutMinutes",
ResourceRef: []config.ResourceReference{},

View File

@ -6,95 +6,32 @@ import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strings"
"time"
pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util"
piperHttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/SAP/jenkins-library/pkg/protecode"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/fake"
)
type DockerClientMock struct {
imageName string
registryURL string
localPath string
includeLayers bool
}
//Download interface for download an image to a local path
type Download interface {
GetImageSource() (string, error)
DownloadImageToPath(imageSource, filePath string) (pkgutil.Image, error)
TarImage(writer io.Writer, image pkgutil.Image) error
}
const (
daemonPrefix = "daemon://"
remotePrefix = "remote://"
)
func (c *DockerClientMock) GetImageSource() (string, error) {
imageSource := c.imageName
if len(c.registryURL) > 0 && len(c.localPath) <= 0 {
registry := c.registryURL
url, _ := url.Parse(c.registryURL)
//remove protocoll from registryURL to get registry
if len(url.Scheme) > 0 {
registry = strings.Replace(c.registryURL, fmt.Sprintf("%v://", url.Scheme), "", 1)
}
if strings.HasSuffix(registry, "/") {
imageSource = fmt.Sprintf("%v%v%v", remotePrefix, registry, c.imageName)
} else {
imageSource = fmt.Sprintf("%v%v/%v", remotePrefix, registry, c.imageName)
}
} else if len(c.localPath) > 0 {
imageSource = c.localPath
if !pkgutil.IsTar(c.localPath) {
imageSource = fmt.Sprintf("%v%v", daemonPrefix, c.localPath)
}
}
if len(imageSource) <= 0 {
return imageSource, fmt.Errorf("There is no image source for the parameters: (Name: %v, Registry: %v, local Path: %v)", c.imageName, c.registryURL, c.localPath)
}
return imageSource, nil
}
//DownloadImageToPath download the image to the specified path
func (c *DockerClientMock) DownloadImageToPath(imageSource, filePath string) (pkgutil.Image, error) {
return pkgutil.Image{}, nil
}
//TarImage write a tar from the given image
func (c *DockerClientMock) TarImage(writer io.Writer, image pkgutil.Image) error {
return nil
type protecodeTestUtilsBundle struct {
*mock.FilesMock
*mock.DownloadMock
}
func TestRunProtecodeScan(t *testing.T) {
requestURI := ""
dir, err := ioutil.TempDir("", "t")
require.NoError(t, err, "Failed to create temporary directory")
// clean up tmp dir
defer func() { _ = os.RemoveAll(dir) }()
testFile, err := ioutil.TempFile(dir, "t.tar")
require.NoError(t, err)
fileName := filepath.Base(testFile.Name())
path := strings.ReplaceAll(testFile.Name(), fileName, "")
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
requestURI = req.RequestURI
@ -141,24 +78,81 @@ func TestRunProtecodeScan(t *testing.T) {
pc := protecode.Protecode{}
pc.SetOptions(po)
dClient := &DockerClientMock{imageName: "t", registryURL: "", localPath: path, includeLayers: false}
influx := protecodeExecuteScanInflux{}
reportPath = dir
cachePath = dir
t.Run("With tar as scan image", func(t *testing.T) {
config := protecodeExecuteScanOptions{ServerURL: server.URL, TimeoutMinutes: "1", VerifyOnly: false, CleanupMode: "none", Group: "13", FetchURL: "/api/fetch/", ExcludeCVEs: "CVE-2018-1, CVE-2017-1000382", ReportFileName: "./cache/report-file.txt"}
err = runProtecodeScan(&config, &influx, dClient)
assert.NoError(t, err)
ttt := []struct {
name string
opts protecodeExecuteScanOptions
}{
{
name: "With tar as scan image",
opts: protecodeExecuteScanOptions{
ServerURL: server.URL,
TimeoutMinutes: "1",
VerifyOnly: false,
CleanupMode: "none",
Group: "13",
FetchURL: "/api/fetch/",
ExcludeCVEs: "CVE-2018-1, CVE-2017-1000382",
ReportFileName: "./cache/report-file.txt",
},
},
{
name: "Without tar as scan image",
opts: protecodeExecuteScanOptions{
ServerURL: server.URL,
ScanImage: "t",
TimeoutMinutes: "1",
VerifyOnly: false,
CleanupMode: "none",
Group: "13",
ExcludeCVEs: "CVE-2018-1, CVE-2017-1000382",
ReportFileName: "./cache/report-file.txt",
},
},
}
for _, test := range ttt {
t.Run(test.name, func(t *testing.T) {
files := mock.FilesMock{}
httpClient := piperHttp.Client{}
httpClient.SetFileUtils(&files)
pc.SetHttpClient(&httpClient)
docker := mock.DownloadMock{}
docker.Stub = func(imageRef string, dest string) (v1.Image, error) {
files.AddFile(dest, []byte(""))
return &fake.FakeImage{}, nil
}
utils := protecodeTestUtilsBundle{DownloadMock: &docker, FilesMock: &files}
cacheDir, _ := files.TempDir("", "protecode-")
files.AddFile(filepath.Join(cacheDir, "t.tar"), []byte(""))
err := runProtecodeScan(&test.opts, &influx, pc, utils, cacheDir)
if assert.NoError(t, err) {
if protecodeExecuteJsonExists, err := files.FileExists("protecodeExecuteScan.json"); assert.NoError(t, err) {
assert.True(t, protecodeExecuteJsonExists, "protecodeExecuteScan.json expected")
}
if protecodeVulnsJsonExists, err := files.FileExists("protecodescan_vulns.json"); assert.NoError(t, err) {
assert.True(t, protecodeVulnsJsonExists, "protecodescan_vulns.json expected")
}
if userSpecifiedReportExists, err := files.FileExists(test.opts.ReportFileName); assert.NoError(t, err) {
assert.True(t, userSpecifiedReportExists, "%s must exist", test.opts.ReportFileName)
}
}
if cacheExists, err := files.DirExists(cacheDir); assert.NoError(t, err) {
assert.True(t, cacheExists, "Whoever calls runProtecodeScan is responsible to cleanup the cache.")
}
})
t.Run("Without tar as scan image", func(t *testing.T) {
config := protecodeExecuteScanOptions{ServerURL: server.URL, ScanImage: "t", TimeoutMinutes: "1", VerifyOnly: false, CleanupMode: "none", Group: "13", ExcludeCVEs: "CVE-2018-1, CVE-2017-1000382", ReportFileName: "./cache/report-file.txt"}
err = runProtecodeScan(&config, &influx, dClient)
assert.NoError(t, err)
})
}
}
func TestHandleArtifactVersion(t *testing.T) {
@ -175,13 +169,15 @@ func TestHandleArtifactVersion(t *testing.T) {
{"7.20.20", "7.20.20"},
}
for _, c := range cases {
for i, c := range cases {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
got := handleArtifactVersion(c.version)
assert.Equal(t, c.want, got)
})
}
}
func TestCreateClient(t *testing.T) {
func TestCreateProtecodeClient(t *testing.T) {
cases := []struct {
timeout string
}{
@ -190,28 +186,12 @@ func TestCreateClient(t *testing.T) {
}
for _, c := range cases {
t.Run(fmt.Sprintf("With timeout: %s", c.timeout), func(t *testing.T) {
config := protecodeExecuteScanOptions{TimeoutMinutes: c.timeout}
client := createClient(&config)
assert.NotNil(t, client, "client should not be empty")
}
}
func TestCreateDockerClient(t *testing.T) {
cases := []struct {
scanImage string
dockerRegistryURL string
filePath string
includeLayers bool
}{
{"test", "url", "path", false},
{"", "", "", true},
}
for _, c := range cases {
config := protecodeExecuteScanOptions{ScanImage: c.scanImage, DockerRegistryURL: c.dockerRegistryURL, FilePath: c.filePath, IncludeLayers: c.includeLayers}
client := createDockerClient(&config)
client := createProtecodeClient(&config)
assert.NotNil(t, client, "client should not be empty")
})
}
}
@ -270,20 +250,23 @@ func TestUploadScanOrDeclareFetch(t *testing.T) {
{false, "test", "group1", "", path, "", 0, 4711},
}
for _, c := range cases {
for i, c := range cases {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
utils := protecodeTestUtilsBundle{
FilesMock: &mock.FilesMock{},
DownloadMock: &mock.DownloadMock{},
}
// test
config := protecodeExecuteScanOptions{VerifyOnly: c.reuse, CleanupMode: c.clean, Group: c.group, FetchURL: c.fetchURL, FilePath: c.filePath}
// got := uploadScanOrDeclareFetch(config, 0, pc, fileName)
got := uploadScanOrDeclareFetch(config, c.prID, pc, fileName)
got := uploadScanOrDeclareFetch(utils, config, c.prID, pc, fileName)
// assert
assert.Equal(t, c.want, got)
})
}
}
func writeReportToFileMock(resp io.ReadCloser, reportFileName string) error {
return nil
}
func TestExecuteProtecodeScan(t *testing.T) {
testDataFile := filepath.Join("testdata", "TestProtecode", "protecode_result_violations.json")
violationsAbsPath, err := filepath.Abs(testDataFile)
@ -333,26 +316,23 @@ func TestExecuteProtecodeScan(t *testing.T) {
require.NoErrorf(t, err, "Failed to get current directory: %v", err)
defer func() { _ = os.Chdir(resetDir) }()
for _, c := range cases {
// init
dir, err := ioutil.TempDir("", "t")
require.NoErrorf(t, err, "Failed to create temporary directory: %v", err)
// clean up tmp dir
defer func() { _ = os.RemoveAll(dir) }()
// change into tmp dir and write test data
err = os.Chdir(dir)
require.NoErrorf(t, err, "Failed to change into temporary directory: %v", err)
reportPath = dir
for i, c := range cases {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
utils := protecodeTestUtilsBundle{
FilesMock: &mock.FilesMock{},
DownloadMock: &mock.DownloadMock{},
}
config := protecodeExecuteScanOptions{VerifyOnly: c.reuse, CleanupMode: c.clean, Group: c.group, FetchURL: c.fetchURL, TimeoutMinutes: "3", ExcludeCVEs: "CVE-2018-1, CVE-2017-1000382", ReportFileName: "./cache/report-file.txt"}
influxData := &protecodeExecuteScanInflux{}
// test
executeProtecodeScan(influxData, pc, &config, "dummy", writeReportToFileMock)
executeProtecodeScan(influxData, pc, &config, "dummy", utils)
// assert
assert.Equal(t, 1125, influxData.protecode_data.fields.historical_vulnerabilities)
assert.Equal(t, 0, influxData.protecode_data.fields.triaged_vulnerabilities)
assert.Equal(t, 1, influxData.protecode_data.fields.excluded_vulnerabilities)
assert.Equal(t, 142, influxData.protecode_data.fields.major_vulnerabilities)
assert.Equal(t, 226, influxData.protecode_data.fields.vulnerabilities)
})
}
}
@ -390,42 +370,3 @@ func TestCorrectDockerConfigEnvVar(t *testing.T) {
assert.Equal(t, resetValue, os.Getenv("DOCKER_CONFIG"))
})
}
func TestGetTarName(t *testing.T) {
cases := map[string]struct {
image string
version string
expect string
}{
"with version suffix": {
"com.sap.piper/sample-k8s-app-multistage:1.11-20200902040158_97a5cc34f1796ad735159f020dd55c0f3670a4cb",
"1.11-20200902040158_97a5cc34f1796ad735159f020dd55c0f3670a4cb",
"com.sap.piper_sample-k8s-app-multistage_1.tar",
},
"without version suffix": {
"abc",
"3.20.20-20200131085038+eeb7c1033339bfd404d21ec5e7dc05c80e9e985e",
"abc_3.tar",
},
"without version": {
"abc",
"",
"abc.tar",
},
"ScanImage without sha as artifactVersion": {
"abc@sha256:12345",
"",
"abc.tar",
},
"ScanImage with sha as artifactVersion": {
"ppiper/cf-cli@sha256:c25dbacb9ab6e912afe0fe926d8f9d949c60adfe55d16778bde5941e6c37be11",
"c25dbacb9ab6e912afe0fe926d8f9d949c60adfe55d16778bde5941e6c37be11",
"ppiper_cf-cli_c25dbacb9ab6e912afe0fe926d8f9d949c60adfe55d16778bde5941e6c37be11.tar",
},
}
for name, c := range cases {
t.Run(name, func(t *testing.T) {
assert.Equal(t, c.expect, getTarName(&protecodeExecuteScanOptions{ScanImage: c.image, Version: c.version}))
})
}
}

14
cmd/utils.go Normal file
View File

@ -0,0 +1,14 @@
package cmd
import (
"os"
)
// Deprecated: Please use piperutils.Files{} instead
func fileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}

View File

@ -177,10 +177,9 @@ func runWhitesourceScan(config *ScanOptions, scan *ws.Scan, utils whitesourceUti
ContainerRegistryUser: config.ContainerRegistryUser,
ContainerRegistryPassword: config.ContainerRegistryPassword,
DockerConfigJSON: config.DockerConfigJSON,
IncludeLayers: config.ScanImageIncludeLayers,
FilePath: config.ProjectName,
}
dClientOptions := piperDocker.ClientOptions{ImageName: saveImageOptions.ContainerImage, RegistryURL: saveImageOptions.ContainerRegistryURL, LocalPath: "", IncludeLayers: saveImageOptions.IncludeLayers}
dClientOptions := piperDocker.ClientOptions{ImageName: saveImageOptions.ContainerImage, RegistryURL: saveImageOptions.ContainerRegistryURL, LocalPath: ""}
dClient := &piperDocker.Client{}
dClient.SetOptions(dClientOptions)
if _, err := runContainerSaveImage(&saveImageOptions, &telemetry.CustomData{}, "./cache", "", dClient, utils); err != nil {

View File

@ -52,7 +52,6 @@ type whitesourceExecuteScanOptions struct {
ProjectToken string `json:"projectToken,omitempty"`
Reporting bool `json:"reporting,omitempty"`
ScanImage string `json:"scanImage,omitempty"`
ScanImageIncludeLayers bool `json:"scanImageIncludeLayers,omitempty"`
ScanImageRegistryURL string `json:"scanImageRegistryUrl,omitempty"`
SecurityVulnerabilities bool `json:"securityVulnerabilities,omitempty"`
ServiceURL string `json:"serviceUrl,omitempty"`
@ -331,7 +330,6 @@ func addWhitesourceExecuteScanFlags(cmd *cobra.Command, stepConfig *whitesourceE
cmd.Flags().StringVar(&stepConfig.ProjectToken, "projectToken", os.Getenv("PIPER_projectToken"), "Project token to execute scan on. Ignored for scan types `maven`, `mta` and `npm`. Used for project aggregation when scanning with the Unified Agent and can be provided as an alternative to `projectName`.")
cmd.Flags().BoolVar(&stepConfig.Reporting, "reporting", true, "Whether assessment is being done at all, defaults to `true`")
cmd.Flags().StringVar(&stepConfig.ScanImage, "scanImage", os.Getenv("PIPER_scanImage"), "For `buildTool: docker`: Defines the docker image which should be scanned.")
cmd.Flags().BoolVar(&stepConfig.ScanImageIncludeLayers, "scanImageIncludeLayers", true, "For `buildTool: docker`: Defines if layers should be included.")
cmd.Flags().StringVar(&stepConfig.ScanImageRegistryURL, "scanImageRegistryUrl", os.Getenv("PIPER_scanImageRegistryUrl"), "For `buildTool: docker`: Defines the registry where the scanImage is located.")
cmd.Flags().BoolVar(&stepConfig.SecurityVulnerabilities, "securityVulnerabilities", true, "Whether security compliance is considered and reported as part of the assessment.")
cmd.Flags().StringVar(&stepConfig.ServiceURL, "serviceUrl", `https://saas.whitesourcesoftware.com/api`, "URL to the WhiteSource API endpoint.")
@ -712,15 +710,6 @@ func whitesourceExecuteScanMetadata() config.StepData {
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_scanImage"),
},
{
Name: "scanImageIncludeLayers",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "bool",
Mandatory: false,
Aliases: []config.Alias{},
Default: true,
},
{
Name: "scanImageRegistryUrl",
ResourceRef: []config.ResourceReference{

1
go.mod
View File

@ -4,7 +4,6 @@ go 1.17
require (
cloud.google.com/go/storage v1.10.0
github.com/GoogleContainerTools/container-diff v0.17.0
github.com/Jeffail/gabs/v2 v2.6.1
github.com/Masterminds/sprig v2.22.0+incompatible
github.com/antchfx/htmlquery v1.2.4

17
go.sum
View File

@ -63,7 +63,6 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
code.cloudfoundry.org/bytefmt v0.0.0-20180906201452-2aa6f33b730c/go.mod h1:wN/zk7mhREp/oviagqUXY3EwuHhWyOvAdsn5Y4CzOrc=
code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f h1:UrKzEwTgeiff9vxdrfdqxibzpWjxLnuXDI5m6z3GJAk=
code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f/go.mod h1:sk5LnIjB/nIEU7yP5sDQExVm62wu0pBh3yrElngUisI=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
@ -151,8 +150,6 @@ github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q
github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/GoogleContainerTools/container-diff v0.17.0 h1:P9va/peQsiBcFuL+hcZWHp3Ry7bCLLoNhUrjj7DBNFQ=
github.com/GoogleContainerTools/container-diff v0.17.0/go.mod h1:pddOdZb5Wghtb2fmqbKy7Kj/sgoafW9AvUp4KS9svmg=
github.com/Jeffail/gabs v1.1.1 h1:V0uzR08Hj22EX8+8QMhyI9sX2hwRu+/RJhJUmnwda/E=
github.com/Jeffail/gabs v1.1.1/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc=
github.com/Jeffail/gabs/v2 v2.6.1 h1:wwbE6nTQTwIMsMxzi6XFQQYRZ6wDc1mSdxoAN+9U4Gk=
@ -448,7 +445,6 @@ github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTV
github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c=
github.com/containerd/containerd v1.5.9 h1:rs6Xg1gtIxaeyG+Smsb/0xaSDu1VgFhOCKBXxMxbsF4=
github.com/containerd/containerd v1.5.9/go.mod h1:fvQqCfadDGga5HZyn3j4+dx56qj2I9YwBrlSdalvJYQ=
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
@ -583,12 +579,9 @@ github.com/docker/cli v20.10.9+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHv
github.com/docker/cli v20.10.11+incompatible h1:tXU1ezXcruZQRrMP8RN2z9N91h+6egZTS1gsPsKantc=
github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
github.com/docker/distribution v0.0.0-20200319173657-742aab907b54/go.mod h1:Oqz4IonmMNc2N7GqfTL2xkhCQx0yS6nR+HrOZJnmKIk=
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v0.7.3-0.20190212235812-0111ee70874a/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v1.4.2-0.20190219180918-740349757396/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v1.4.2-0.20200319182547-c7ad2b866182/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v20.10.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
@ -683,7 +676,6 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/fsouza/go-dockerclient v1.3.6/go.mod h1:ptN6nXBwrXuiHAz2TYGOFCBB1aKGr371sGjMFdJEr1A=
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
github.com/gabriel-vasile/mimetype v1.3.1 h1:qevA6c2MtE1RorlScnixeG0VA1H4xrXyhyX3oWBynNQ=
@ -983,7 +975,6 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-containerregistry v0.0.0-20190214194807-bada66e31e55/go.mod h1:yZAFP63pRshzrEYLXLGPmUt0Ay+2zdjmMN1loCnRLUk=
github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0=
github.com/google/go-containerregistry v0.5.2-0.20210604130445-3bfab55f3bd9/go.mod h1:R5WRYyTdQqTchlBhX4q+WICGh8HQIL5wDFoFZv7Jq6Q=
github.com/google/go-containerregistry v0.6.0 h1:niQ+8XD//kKgArIFwDVBXsWVWbde16LPdHMyNwSC8h4=
@ -1051,7 +1042,6 @@ github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
@ -1312,7 +1302,6 @@ github.com/huaweicloud/golangsdk v0.0.0-20200304081349-45ec0797f2a4/go.mod h1:WQ
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ijc/Gotty v0.0.0-20170406111628-a8b993ba6abd/go.mod h1:3LVOLeyx9XVvwPgrt2be44XgSqndprz1G18rSk8KD84=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
@ -1639,7 +1628,6 @@ github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/
github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s=
github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443/go.mod h1:JbxfV1Iifij2yhRjXai0oFrbpxszXHRx1E5RuM26o4Y=
github.com/nwaples/rardecode v1.1.2/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
@ -1765,7 +1753,6 @@ github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -1882,7 +1869,6 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@ -2128,7 +2114,6 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@ -2150,7 +2135,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -2343,7 +2327,6 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190109145017-48ac38b7c8cb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View File

@ -46,12 +46,12 @@ func DownloadBuildpacks(path string, bpacks []string, dockerCreds string, utils
defer utils.RemoveAll(tempDir)
log.Entry().Infof("Downloading buildpack '%s' to %s", bpack, tempDir)
img, err := utils.DownloadImageToPath(bpack, tempDir)
img, err := utils.DownloadImageContent(bpack, tempDir)
if err != nil {
return Order{}, fmt.Errorf("failed download buildpack image '%s', error: %s", bpack, err.Error())
}
imgConf, err := img.Image.ConfigFile()
imgConf, err := img.ConfigFile()
if err != nil {
return Order{}, fmt.Errorf("failed to read '%s' image config, error: %s", bpack, err.Error())
}

View File

@ -4,10 +4,9 @@
package cnbutils
import (
"io"
"fmt"
"path/filepath"
pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/SAP/jenkins-library/pkg/piperutils"
v1 "github.com/google/go-containerregistry/pkg/v1"
@ -23,7 +22,7 @@ func (c *MockUtils) GetFileUtils() piperutils.FileUtils {
return c.FilesMock
}
func (c *MockUtils) DownloadImageToPath(bpack, filePath string) (pkgutil.Image, error) {
func (c *MockUtils) DownloadImageContent(bpack, targetDir string) (v1.Image, error) {
fakeImage := fakeImage.FakeImage{}
fakeImage.ConfigFileReturns(&v1.ConfigFile{
Config: v1.Config{
@ -33,18 +32,15 @@ func (c *MockUtils) DownloadImageToPath(bpack, filePath string) (pkgutil.Image,
},
}, nil)
c.AddDir(filepath.Join(filePath, "cnb/buildpacks", bpack))
c.AddDir(filepath.Join(filePath, "cnb/buildpacks", bpack, "0.0.1"))
img := pkgutil.Image{
Image: &fakeImage,
}
return img, nil
c.AddDir(filepath.Join(targetDir, "cnb/buildpacks", bpack))
c.AddDir(filepath.Join(targetDir, "cnb/buildpacks", bpack, "0.0.1"))
return &fakeImage, nil
}
func (c *MockUtils) DownloadImage(src, dst string) (v1.Image, error) {
return nil, fmt.Errorf("not implemented")
}
func (c *MockUtils) GetImageSource() (string, error) {
return "imageSource", nil
}
func (c *MockUtils) TarImage(writer io.Writer, image pkgutil.Image) error {
return nil
}

View File

@ -4,17 +4,18 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/url"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util"
"github.com/google/go-containerregistry/pkg/legacy/tarball"
cranecmd "github.com/google/go-containerregistry/cmd/crane/cmd"
"github.com/google/go-containerregistry/pkg/crane"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1"
)
// AuthEntry defines base64 encoded username:password required inside a Docker config.json
@ -87,84 +88,106 @@ type ClientOptions struct {
ImageName string
RegistryURL string
LocalPath string
IncludeLayers bool
}
//Download interface for download an image to a local path
type Download interface {
GetImageSource() (string, error)
DownloadImageToPath(imageSource, filePath string) (pkgutil.Image, error)
TarImage(writer io.Writer, image pkgutil.Image) error
DownloadImage(imageSource, targetFile string) (v1.Image, error)
DownloadImageContent(imageSource, targetDir string) (v1.Image, error)
}
// SetOptions sets options used for the docker client
func (c *Client) SetOptions(options ClientOptions) {
c.imageName = options.ImageName
c.registryURL = options.RegistryURL
c.includeLayers = options.IncludeLayers
c.localPath = options.LocalPath
}
const (
daemonPrefix = "daemon://"
remotePrefix = "remote://"
)
//DownloadImageToPath downloads the image content into the given targetDir. Returns with an error if the targetDir doesnt exist
func (c *Client) DownloadImageContent(imageSource, targetDir string) (v1.Image, error) {
if fileInfo, err := os.Stat(targetDir); err != nil {
return nil, err
} else if !fileInfo.IsDir() {
return nil, fmt.Errorf("specified target is not a directory: %s", targetDir)
}
//GetImageSource get the image source from client attributes (localPath, imageName, registryURL)
func (c *Client) GetImageSource() (string, error) {
noOpts := []crane.Option{}
imageSource := c.imageName
if len(c.registryURL) > 0 && len(c.localPath) <= 0 {
registry := c.registryURL
url, err := url.Parse(c.registryURL)
imageRef, err := c.getImageRef(imageSource)
if err != nil {
return "", fmt.Errorf("failed to parse registryURL %v: %w", c.registryURL, err)
return nil, err
}
//remove protocol from registryURL to get registry
if len(url.Scheme) > 0 {
registry = strings.Replace(c.registryURL, fmt.Sprintf("%v://", url.Scheme), "", 1)
img, err := crane.Pull(imageRef.Name(), noOpts...)
if err != nil {
return nil, err
}
if strings.HasSuffix(registry, "/") {
imageSource = fmt.Sprintf("%v%v%v", remotePrefix, registry, c.imageName)
} else {
imageSource = fmt.Sprintf("%v%v/%v", remotePrefix, registry, c.imageName)
}
} else if len(c.localPath) > 0 {
imageSource = c.localPath
if !pkgutil.IsTar(c.localPath) {
imageSource = fmt.Sprintf("%v%v", daemonPrefix, c.localPath)
tmpFile, err := os.CreateTemp(".", ".piper-download-")
if err != nil {
return nil, err
}
defer os.Remove(tmpFile.Name())
args := []string{imageRef.Name(), tmpFile.Name()}
exportCmd := cranecmd.NewCmdExport(&noOpts)
exportCmd.SetArgs(args)
if err := exportCmd.Execute(); err != nil {
return nil, err
}
if len(imageSource) <= 0 {
return imageSource, fmt.Errorf("no image found for the parameters: (Name: %v, Registry: %v, local Path: %v)", c.imageName, c.registryURL, c.localPath)
}
return imageSource, nil
return img, piperutils.Untar(tmpFile.Name(), targetDir, 0)
}
//DownloadImageToPath download the image to the specified path
func (c *Client) DownloadImageToPath(imageSource, filePath string) (pkgutil.Image, error) {
// DownloadImage downloads the image and saves it as tar at the given path
func (c *Client) DownloadImage(imageSource, targetFile string) (v1.Image, error) {
noOpts := []crane.Option{}
return pkgutil.GetImage(imageSource, c.includeLayers, filePath)
imageRef, err := c.getImageRef(imageSource)
if err != nil {
return nil, err
}
img, err := crane.Pull(imageRef.Name(), noOpts...)
if err != nil {
return nil, err
}
tmpFile, err := os.CreateTemp(".", ".piper-download-")
if err != nil {
return nil, err
}
craneCmd := cranecmd.NewCmdPull(&noOpts)
craneCmd.SetOut(log.Writer())
craneCmd.SetErr(log.Writer())
craneCmd.SetArgs([]string{imageRef.Name(), tmpFile.Name(), "--format=tarball"})
if err := craneCmd.Execute(); err != nil {
defer os.Remove(tmpFile.Name())
return nil, err
}
if err := os.Rename(tmpFile.Name(), targetFile); err != nil {
defer os.Remove(tmpFile.Name())
return nil, err
}
return img, nil
}
//TarImage write a tar from the given image
func (c *Client) TarImage(writer io.Writer, image pkgutil.Image) error {
func (c *Client) getImageRef(image string) (name.Reference, error) {
opts := []name.Option{}
reference, err := name.ParseReference(image.Digest.String(), name.WeakValidation)
if err != nil {
return err
if len(c.registryURL) > 0 {
re := regexp.MustCompile(`(?i)^https?://`)
registry := re.ReplaceAllString(c.registryURL, "")
opts = append(opts, name.WithDefaultRegistry(registry))
}
err = tarball.Write(reference, image.Image, writer)
if err != nil {
return err
}
return nil
return name.ParseReference(image, opts...)
}
// ImageListWithFilePath compiles container image names based on all Dockerfiles found, considering excludes

View File

@ -103,35 +103,6 @@ func TestCreateDockerConfigJSON(t *testing.T) {
})
}
func TestGetImageSource(t *testing.T) {
cases := []struct {
imageName string
registryURL string
localPath string
want string
}{
{"imageName", "", "", "imageName"},
{"imageName", "", "localPath", "daemon://localPath"},
{"imageName", "http://registryURL", "", "remote://registryURL/imageName"},
{"imageName", "https://containerRegistryUrl", "", "remote://containerRegistryUrl/imageName"},
{"imageName", "registryURL", "", "remote://registryURL/imageName"},
}
client := Client{}
for _, c := range cases {
options := ClientOptions{ImageName: c.imageName, RegistryURL: c.registryURL, LocalPath: c.localPath}
client.SetOptions(options)
got, err := client.GetImageSource()
assert.Nil(t, err)
assert.Equal(t, c.want, got)
}
}
func TestImageListWithFilePath(t *testing.T) {
t.Parallel()

View File

@ -13,7 +13,6 @@ import (
"mime/multipart"
"net"
"net/http"
"os"
"path"
"path/filepath"
"strings"
@ -42,6 +41,7 @@ type Client struct {
doLogResponseBodyOnDebug bool
useDefaultTransport bool
trustedCerts []string
fileUtils piperutils.FileUtils
}
// ClientOptions defines the options to be set on the client
@ -110,6 +110,15 @@ type Uploader interface {
Upload(data UploadRequestData) (*http.Response, error)
}
// fileUtils lazy initializes the utils
func (c *Client) getFileUtils() piperutils.FileUtils {
if c.fileUtils == nil {
c.fileUtils = &piperutils.Files{}
}
return c.fileUtils
}
// UploadFile uploads a file's content as multipart-form POST request to the specified URL
func (c *Client) UploadFile(url, file, fileFieldName string, header http.Header, cookies []*http.Cookie, uploadType string) (*http.Response, error) {
return c.UploadRequest(http.MethodPost, url, file, fileFieldName, header, cookies, uploadType)
@ -117,7 +126,8 @@ func (c *Client) UploadFile(url, file, fileFieldName string, header http.Header,
// UploadRequest uploads a file's content as multipart-form with given http method request to the specified URL
func (c *Client) UploadRequest(method, url, file, fileFieldName string, header http.Header, cookies []*http.Cookie, uploadType string) (*http.Response, error) {
fileHandle, err := os.Open(file)
fileHandle, err := c.getFileUtils().Open(file)
if err != nil {
return &http.Response{}, errors.Wrapf(err, "unable to locate file %v", file)
}
@ -245,6 +255,12 @@ func (c *Client) SetOptions(options ClientOptions) {
}
c.cookieJar = options.CookieJar
c.trustedCerts = options.TrustedCerts
c.fileUtils = &piperutils.Files{}
}
// SetFileUtils can be used to overwrite the default file utils
func (c *Client) SetFileUtils(fileUtils piperutils.FileUtils) {
c.fileUtils = fileUtils
}
// StandardClient returns a stdlib *http.Client which respects the custom settings.
@ -500,7 +516,6 @@ func (c *Client) applyDefaults() {
func (c *Client) configureTLSToTrustCertificates(transport *TransportWrapper) error {
trustStoreDir, err := getWorkingDirForTrustStore()
fileUtils := &piperutils.Files{}
if err != nil {
return errors.Wrap(err, "failed to create trust store directory")
}
@ -539,7 +554,7 @@ func (c *Client) configureTLSToTrustCertificates(transport *TransportWrapper) er
filename := path.Base(certificate)
filename = strings.ReplaceAll(filename, " ", "")
target := filepath.Join(trustStoreDir, filename)
if exists, _ := fileUtils.FileExists(target); !exists {
if exists, _ := c.getFileUtils().FileExists(target); !exists {
log.Entry().WithField("source", certificate).WithField("target", target).Info("Downloading TLS certificate")
request, err := http.NewRequest("GET", certificate, nil)
if err != nil {
@ -561,11 +576,11 @@ func (c *Client) configureTLSToTrustCertificates(transport *TransportWrapper) er
defer response.Body.Close()
parent := filepath.Dir(target)
if len(parent) > 0 {
if err = os.MkdirAll(parent, 0777); err != nil {
if err = c.getFileUtils().MkdirAll(parent, 0777); err != nil {
return err
}
}
fileHandler, err := os.Create(target)
fileHandler, err := c.getFileUtils().Create(target)
if err != nil {
return errors.Wrapf(err, "unable to create file %v", filename)
}

44
pkg/mock/dockerClient.go Normal file
View File

@ -0,0 +1,44 @@
package mock
import (
"fmt"
"github.com/google/go-containerregistry/pkg/v1"
)
// DownloadMock .
type DownloadMock struct {
FilePath string
ImageRef string
RegistryURL string
ReturnImage v1.Image
ReturnError string
Stub func(imageRef, targetDir string) (v1.Image, error)
}
// DownloadImage .
func (c *DownloadMock) DownloadImage(imageRef, targetDir string) (v1.Image, error) {
c.ImageRef = imageRef
c.FilePath = targetDir
if c.Stub != nil {
return c.Stub(imageRef, targetDir)
}
if len(c.ReturnError) > 0 {
return nil, fmt.Errorf(c.ReturnError)
}
return c.ReturnImage, nil
}
// DownloadImageContent .
func (c *DownloadMock) DownloadImageContent(imageRef, targetFile string) (v1.Image, error) {
c.ImageRef = imageRef
c.FilePath = targetFile
if len(c.ReturnError) > 0 {
return nil, fmt.Errorf(c.ReturnError)
}
return c.ReturnImage, nil
}

View File

@ -6,6 +6,7 @@ package mock
import (
"crypto/sha256"
"fmt"
"io"
"os"
"path/filepath"
"sort"
@ -538,11 +539,15 @@ type FileMock struct {
// Reads the content of the mock
func (f *FileMock) Read(b []byte) (n int, err error) {
if len(b) == 0 {
return 0, nil
}
for i, p := range f.content {
b[i] = p
}
return len(f.content), nil
return len(f.content), io.EOF
}
// Close mocks freeing the associated OS resources.
@ -610,3 +615,11 @@ func (f *FilesMock) OpenFile(path string, flag int, perm os.FileMode) (*FileMock
return &file, nil
}
func (f *FilesMock) Open(name string) (io.ReadWriteCloser, error) {
return f.OpenFile(name, os.O_RDONLY, 0)
}
func (f *FilesMock) Create(name string) (io.ReadWriteCloser, error) {
return f.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
}

View File

@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"io/ioutil"
"os"
"path/filepath"
@ -38,6 +39,8 @@ type FileUtils interface {
Symlink(oldname string, newname string) error
SHA256(path string) (string, error)
CurrentTime(format string) string
Open(name string) (io.ReadWriteCloser, error)
Create(name string) (io.ReadWriteCloser, error)
}
// Files ...
@ -230,20 +233,29 @@ func Unzip(src, dest string) ([]string, error) {
func Untar(src string, dest string, stripComponentLevel int) error {
file, err := os.Open(src)
defer file.Close()
if err != nil {
fmt.Errorf("unable to open src: %v", err)
}
if b, err := isFileGzipped(src); err != nil && b {
zr, err := gzip.NewReader(file)
if err != nil {
return fmt.Errorf("requires gzip-compressed body: %v", err)
}
return untar(zr, dest, stripComponentLevel)
}
return untar(file, dest, stripComponentLevel)
}
func untar(r io.Reader, dir string, level int) (err error) {
madeDir := map[string]bool{}
zr, err := gzip.NewReader(r)
if err != nil {
return fmt.Errorf("requires gzip-compressed body: %v", err)
}
tr := tar.NewReader(zr)
tr := tar.NewReader(r)
for {
f, err := tr.Next()
if err == io.EOF {
@ -252,7 +264,10 @@ func untar(r io.Reader, dir string, level int) (err error) {
if err != nil {
return fmt.Errorf("tar error: %v", err)
}
if !validRelPath(f.Name) {
if strings.HasPrefix(f.Name, "/") {
f.Name = fmt.Sprintf(".%s", f.Name)
}
if !validRelPath(f.Name) { // blocks path traversal attacks
return fmt.Errorf("tar contained invalid name error %q", f.Name)
}
rel := filepath.FromSlash(f.Name)
@ -304,6 +319,10 @@ func untar(r io.Reader, dir string, level int) (err error) {
return err
}
madeDir[abs] = true
case mode&fs.ModeSymlink != 0:
if err := os.Symlink(f.Linkname, abs); err != nil {
return err
}
default:
return fmt.Errorf("tar file entry %s contained unsupported file type %v", f.Name, mode)
}
@ -311,6 +330,25 @@ func untar(r io.Reader, dir string, level int) (err error) {
return nil
}
// isFileGzipped checks the first 3 bytes of the given file to determine if it is gzipped or not. Returns `true` if the file is gzipped.
func isFileGzipped(file string) (bool, error) {
f, err := os.Open(file)
defer f.Close()
if err != nil {
return false, err
}
b := make([]byte, 3)
_, err = io.ReadFull(f, b)
if err != nil {
return false, err
}
return b[0] == 0x1f && b[1] == 0x8b && b[2] == 8, nil
}
func validRelPath(p string) bool {
if p == "" || strings.Contains(p, `\`) || strings.HasPrefix(p, "/") || strings.Contains(p, "../") {
return false
@ -442,3 +480,13 @@ func (f Files) CurrentTime(format string) string {
}
return fmt.Sprint(time.Now().Format(fString))
}
// Open is a wrapper for os.Open
func (f Files) Open(name string) (io.ReadWriteCloser, error) {
return os.Open(name)
}
// Create is a wrapper for os.Create
func (f Files) Create(name string) (io.ReadWriteCloser, error) {
return os.Create(name)
}

View File

@ -126,6 +126,11 @@ func (pc *Protecode) SetOptions(options Options) {
pc.client.SetOptions(httpOptions)
}
//SetHttpClient setter function to set the http client
func (pc *Protecode) SetHttpClient(client piperHttp.Uploader) {
pc.client = client
}
func (pc *Protecode) createURL(path string, pValue string, fParam string) string {
protecodeURL, err := url.Parse(pc.serverURL)
@ -339,7 +344,7 @@ func (pc *Protecode) UploadScanFile(cleanupMode, group, filePath, fileName, vers
r, err := pc.client.UploadRequest(http.MethodPut, uploadURL, filePath, "file", headers, nil, "binary")
if err != nil {
//TODO: bubble up error
pc.logger.WithError(err).Fatalf("Error during %v upload request", uploadURL)
pc.logger.WithError(err).Fatalf("Error during upload request %v", uploadURL)
} else {
pc.logger.Info("Upload successful")
}

View File

@ -4,7 +4,6 @@ import (
"crypto/sha1"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"time"
@ -33,7 +32,7 @@ type ReportData struct {
}
// WriteReport ...
func WriteReport(data ReportData, reportPath string, reportFileName string, result map[string]int, writeToFile func(f string, d []byte, p os.FileMode) error) error {
func WriteReport(data ReportData, reportPath string, reportFileName string, result map[string]int, fileUtils piperutils.FileUtils) error {
data.Mandatory = true
data.Count = fmt.Sprintf("%v", result["count"])
data.Cvss2GreaterOrEqualSeven = fmt.Sprintf("%v", result["cvss2GreaterOrEqualSeven"])
@ -46,15 +45,15 @@ func WriteReport(data ReportData, reportPath string, reportFileName string, resu
data.Count, data.Cvss2GreaterOrEqualSeven, data.Cvss3GreaterOrEqualSeven,
data.ExcludedVulnerabilities, data.ExcludeCVEs, data.TriagedVulnerabilities,
data.HistoricalVulnerabilities, data.Vulnerabilities)
return writeJSON(reportPath, reportFileName, data, writeToFile)
return writeJSON(reportPath, reportFileName, data, fileUtils)
}
func writeJSON(path, name string, data interface{}, writeToFile func(f string, d []byte, p os.FileMode) error) error {
func writeJSON(path, name string, data interface{}, fileUtils piperutils.FileUtils) error {
jsonData, err := json.Marshal(data)
if err != nil {
return err
}
return writeToFile(filepath.Join(path, name), jsonData, 0644)
return fileUtils.FileWrite(filepath.Join(path, name), jsonData, 0644)
}
func CreateCustomReport(productName string, productID int, data map[string]int, vulns []Vuln) reporting.ScanReport {
@ -98,18 +97,17 @@ func CreateCustomReport(productName string, productID int, data map[string]int,
return scanReport
}
func WriteCustomReports(scanReport reporting.ScanReport, projectName, projectID string) ([]piperutils.Path, error) {
utils := piperutils.Files{}
func WriteCustomReports(scanReport reporting.ScanReport, projectName, projectID string, fileUtils piperutils.FileUtils) ([]piperutils.Path, error) {
reportPaths := []piperutils.Path{}
// ignore templating errors since template is in our hands and issues will be detected with the automated tests
htmlReport, _ := scanReport.ToHTML()
htmlReportPath := filepath.Join(ReportsDirectory, "piper_protecode_report.html")
// Ensure reporting directory exists
if err := utils.MkdirAll(ReportsDirectory, 0777); err != nil {
if err := fileUtils.MkdirAll(ReportsDirectory, 0777); err != nil {
return reportPaths, errors.Wrapf(err, "failed to create report directory")
}
if err := utils.FileWrite(htmlReportPath, htmlReport, 0666); err != nil {
if err := fileUtils.FileWrite(htmlReportPath, htmlReport, 0666); err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return reportPaths, errors.Wrapf(err, "failed to write html report")
}
@ -118,13 +116,13 @@ func WriteCustomReports(scanReport reporting.ScanReport, projectName, projectID
// JSON reports are used by step pipelineCreateSummary in order to e.g. prepare an issue creation in GitHub
// ignore JSON errors since structure is in our hands
jsonReport, _ := scanReport.ToJSON()
if exists, _ := utils.DirExists(reporting.StepReportDirectory); !exists {
err := utils.MkdirAll(reporting.StepReportDirectory, 0777)
if exists, _ := fileUtils.DirExists(reporting.StepReportDirectory); !exists {
err := fileUtils.MkdirAll(reporting.StepReportDirectory, 0777)
if err != nil {
return reportPaths, errors.Wrap(err, "failed to create reporting directory")
}
}
if err := utils.FileWrite(filepath.Join(reporting.StepReportDirectory, fmt.Sprintf("protecodeExecuteScan_osvm_%v.json", reportShaProtecode([]string{projectName, projectID}))), jsonReport, 0666); err != nil {
if err := fileUtils.FileWrite(filepath.Join(reporting.StepReportDirectory, fmt.Sprintf("protecodeExecuteScan_osvm_%v.json", reportShaProtecode([]string{projectName, projectID}))), jsonReport, 0666); err != nil {
return reportPaths, errors.Wrapf(err, "failed to write json report")
}
// we do not add the json report to the overall list of reports for now,

View File

@ -2,20 +2,15 @@ package protecode
import (
"fmt"
"os"
"testing"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/stretchr/testify/assert"
)
var fileContent string
func writeToFileMock(f string, d []byte, p os.FileMode) error {
fileContent = string(d)
return nil
}
func TestWriteReport(t *testing.T) {
files := mock.FilesMock{}
expected := "{\"target\":\"REPORTFILENAME\",\"mandatory\":true,\"productID\":\"4711\",\"serverUrl\":\"DUMMYURL\",\"count\":\"0\",\"cvss2GreaterOrEqualSeven\":\"4\",\"cvss3GreaterOrEqualSeven\":\"3\",\"excludedVulnerabilities\":\"2\",\"triagedVulnerabilities\":\"0\",\"historicalVulnerabilities\":\"1\",\"Vulnerabilities\":[{\"cve\":\"Vulnerability\",\"cvss\":2.5,\"cvss3_score\":\"5.5\"}]}"
var parsedResult map[string]int = make(map[string]int)
@ -25,7 +20,11 @@ func TestWriteReport(t *testing.T) {
parsedResult["cvss2GreaterOrEqualSeven"] = 4
parsedResult["vulnerabilities"] = 5
err := WriteReport(ReportData{ServerURL: "DUMMYURL", FailOnSevereVulnerabilities: false, ExcludeCVEs: "", Target: "REPORTFILENAME", ProductID: fmt.Sprintf("%v", 4711), Vulnerabilities: []Vuln{{"Vulnerability", 2.5, "5.5"}}}, ".", "", parsedResult, writeToFileMock)
assert.Equal(t, fileContent, expected, "content should be not empty")
err := WriteReport(ReportData{ServerURL: "DUMMYURL", FailOnSevereVulnerabilities: false, ExcludeCVEs: "", Target: "REPORTFILENAME", ProductID: fmt.Sprintf("%v", 4711), Vulnerabilities: []Vuln{{"Vulnerability", 2.5, "5.5"}}}, ".", "report.json", parsedResult, &files)
if assert.NoError(t, err) {
content, err := files.FileRead("report.json")
assert.NoError(t, err)
assert.Equal(t, expected, string(content))
}
}

View File

@ -19,7 +19,7 @@ spec:
aliases:
- name: dockerRegistryUrl
type: string
description: The reference to the container registry where the image is located.
description: "For `buildTool: docker`: Url of the container registry - typically provided by the CI/CD environment."
mandatory: true
resourceRef:
- name: commonPipelineEnvironment
@ -72,14 +72,7 @@ spec:
param: custom/repositoryUsername
- name: filePath
type: string
description: The path to the file to which the image should be saved. Defaults to `containerImage.tar`
scope:
- PARAMETERS
- STAGES
- STEPS
- name: includeLayers
type: bool
description: Flag if the docker layers should be included
description: The path to the file to which the image should be saved.
scope:
- PARAMETERS
- STAGES

View File

@ -114,14 +114,6 @@ spec:
- PARAMETERS
- STAGES
- STEPS
- name: scanImageIncludeLayers
type: bool
description: "For `buildTool: docker`: Defines if layers should be included."
scope:
- PARAMETERS
- STAGES
- STEPS
default: true
- name: scanImageRegistryUrl
type: string
description: "For `buildTool: docker`: Defines the registry where the scanImage is located."

View File

@ -99,13 +99,6 @@ spec:
- PARAMETERS
- STAGES
- STEPS
- name: includeLayers
type: bool
description: Flag if the docker layers should be included
scope:
- PARAMETERS
- STAGES
- STEPS
- name: timeoutMinutes
aliases:
- name: protecodeTimeoutMinutes

View File

@ -353,14 +353,6 @@ spec:
- PARAMETERS
- STAGES
- STEPS
- name: scanImageIncludeLayers
type: bool
description: "For `buildTool: docker`: Defines if layers should be included."
scope:
- PARAMETERS
- STAGES
- STEPS
default: true
- name: scanImageRegistryUrl
type: string
description: "For `buildTool: docker`: Defines the registry where the scanImage is located."