1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-10-30 23:57:50 +02:00

Convert containerExecuteStructureTests to go implementation (#2701)

* Converted containerExecuteStructureTests to go implementation

* Added tests

* Fixed issues

* Made fixes

Co-authored-by: lndrschlz <leander.schulz01@sap.com>
Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com>
This commit is contained in:
Siarhei Pazdniakou
2021-03-25 10:18:30 +03:00
committed by GitHub
parent dfe9cb6149
commit 33699c7388
11 changed files with 562 additions and 302 deletions

View File

@@ -0,0 +1,93 @@
package cmd
import (
"fmt"
"io"
"os"
"github.com/SAP/jenkins-library/pkg/command"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/pkg/errors"
)
type containerExecuteStructureTestsUtils interface {
Stdout(out io.Writer)
Stderr(err io.Writer)
RunExecutable(e string, p ...string) error
Glob(pattern string) (matches []string, err error)
}
type containerExecuteStructureTestsUtilsBundle struct {
*command.Command
*piperutils.Files
}
func newContainerExecuteStructureTestsUtils() containerExecuteStructureTestsUtils {
utils := containerExecuteStructureTestsUtilsBundle{
Command: &command.Command{},
Files: &piperutils.Files{},
}
utils.Stdout(log.Writer())
utils.Stderr(log.Writer())
return &utils
}
func containerExecuteStructureTests(config containerExecuteStructureTestsOptions, _ *telemetry.CustomData) {
utils := newContainerExecuteStructureTestsUtils()
err := runContainerExecuteStructureTests(&config, utils)
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
}
func findConfigFiles(pattern string, utils containerExecuteStructureTestsUtils) ([]string, error) {
files, err := utils.Glob(pattern)
if err != nil {
return nil, err
}
return files, nil
}
func runContainerExecuteStructureTests(config *containerExecuteStructureTestsOptions, utils containerExecuteStructureTestsUtils) error {
containerStructureTestsExecutable := "container-structure-test"
var parameters []string
parameters = append(parameters, "test")
configFiles, err := findConfigFiles(config.TestConfiguration, utils)
if err != nil {
return errors.Wrapf(err, "failed to find config files, error: %v", err)
}
if len(configFiles) == 0 {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.New("config files mustn't be missing")
}
for _, config := range configFiles {
parameters = append(parameters, "--config", config)
}
if config.TestDriver != "" {
if config.TestDriver != "docker" && config.TestDriver != "tar" {
log.SetErrorCategory(log.ErrorConfiguration)
return fmt.Errorf("test driver %s is incorrect. Possible drivers: docker, tar", config.TestDriver)
}
parameters = append(parameters, "--driver", config.TestDriver)
} else if os.Getenv("ON_K8S") == "true" {
parameters = append(parameters, "--driver", "tar")
}
if config.PullImage {
parameters = append(parameters, "--pull")
}
parameters = append(parameters, "--image", config.TestImage)
parameters = append(parameters, "--test-report", config.TestReportFilePath)
if GeneralConfig.Verbose {
parameters = append(parameters, "--verbosity", "debug")
}
err = utils.RunExecutable(containerStructureTestsExecutable, parameters...)
if err != nil {
commandLine := append([]string{containerStructureTestsExecutable}, parameters...)
return errors.Wrapf(err, "failed to run executable, command: '%s', error: %v", commandLine, err)
}
return nil
}

View File

@@ -0,0 +1,154 @@
// Code generated by piper's step-generator. DO NOT EDIT.
package cmd
import (
"fmt"
"os"
"time"
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/spf13/cobra"
)
type containerExecuteStructureTestsOptions struct {
PullImage bool `json:"pullImage,omitempty"`
TestConfiguration string `json:"testConfiguration,omitempty"`
TestDriver string `json:"testDriver,omitempty"`
TestImage string `json:"testImage,omitempty"`
TestReportFilePath string `json:"testReportFilePath,omitempty"`
}
// ContainerExecuteStructureTestsCommand In this step [Container Structure Tests](https://github.com/GoogleContainerTools/container-structure-test) are executed.
func ContainerExecuteStructureTestsCommand() *cobra.Command {
const STEP_NAME = "containerExecuteStructureTests"
metadata := containerExecuteStructureTestsMetadata()
var stepConfig containerExecuteStructureTestsOptions
var startTime time.Time
var createContainerExecuteStructureTestsCmd = &cobra.Command{
Use: STEP_NAME,
Short: "In this step [Container Structure Tests](https://github.com/GoogleContainerTools/container-structure-test) are executed.",
Long: `This testing framework allows you to execute different test types against a Docker container, for example:
- Command tests (only if a Docker Deamon is available)
- File existence tests
- File content tests
- Metadata test`,
PreRunE: func(cmd *cobra.Command, _ []string) error {
startTime = time.Now()
log.SetStepName(STEP_NAME)
log.SetVerbose(GeneralConfig.Verbose)
path, _ := os.Getwd()
fatalHook := &log.FatalHook{CorrelationID: GeneralConfig.CorrelationID, Path: path}
log.RegisterHook(fatalHook)
err := PrepareConfig(cmd, &metadata, STEP_NAME, &stepConfig, config.OpenPiperFile)
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return err
}
if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 {
sentryHook := log.NewSentryHook(GeneralConfig.HookConfig.SentryConfig.Dsn, GeneralConfig.CorrelationID)
log.RegisterHook(&sentryHook)
}
return nil
},
Run: func(_ *cobra.Command, _ []string) {
telemetryData := telemetry.CustomData{}
telemetryData.ErrorCode = "1"
handler := func() {
config.RemoveVaultSecretFiles()
telemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds())
telemetryData.ErrorCategory = log.GetErrorCategory().String()
telemetry.Send(&telemetryData)
}
log.DeferExitHandler(handler)
defer handler()
telemetry.Initialize(GeneralConfig.NoTelemetry, STEP_NAME)
containerExecuteStructureTests(stepConfig, &telemetryData)
telemetryData.ErrorCode = "0"
log.Entry().Info("SUCCESS")
},
}
addContainerExecuteStructureTestsFlags(createContainerExecuteStructureTestsCmd, &stepConfig)
return createContainerExecuteStructureTestsCmd
}
func addContainerExecuteStructureTestsFlags(cmd *cobra.Command, stepConfig *containerExecuteStructureTestsOptions) {
cmd.Flags().BoolVar(&stepConfig.PullImage, "pullImage", false, "Force a pull of the tested image before running tests. Only relevant for testDriver 'docker'.")
cmd.Flags().StringVar(&stepConfig.TestConfiguration, "testConfiguration", os.Getenv("PIPER_testConfiguration"), "Container structure test configuration in yml or json format. You can pass a pattern in order to execute multiple tests.")
cmd.Flags().StringVar(&stepConfig.TestDriver, "testDriver", os.Getenv("PIPER_testDriver"), "Container structure test driver to be used for testing, please see https://github.com/GoogleContainerTools/container-structure-test for details.")
cmd.Flags().StringVar(&stepConfig.TestImage, "testImage", os.Getenv("PIPER_testImage"), "Image to be tested")
cmd.Flags().StringVar(&stepConfig.TestReportFilePath, "testReportFilePath", `cst-report.json`, "Path and name of the test report which will be generated")
cmd.MarkFlagRequired("testConfiguration")
cmd.MarkFlagRequired("testImage")
}
// retrieve step metadata
func containerExecuteStructureTestsMetadata() config.StepData {
var theMetaData = config.StepData{
Metadata: config.StepMetadata{
Name: "containerExecuteStructureTests",
Aliases: []config.Alias{},
Description: "In this step [Container Structure Tests](https://github.com/GoogleContainerTools/container-structure-test) are executed.",
},
Spec: config.StepSpec{
Inputs: config.StepInputs{
Parameters: []config.StepParameters{
{
Name: "pullImage",
ResourceRef: []config.ResourceReference{},
Scope: []string{"STEPS", "STAGES", "PARAMETERS"},
Type: "bool",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "testConfiguration",
ResourceRef: []config.ResourceReference{},
Scope: []string{"STEPS", "STAGES", "PARAMETERS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "testDriver",
ResourceRef: []config.ResourceReference{},
Scope: []string{"STEPS", "STAGES", "PARAMETERS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "testImage",
ResourceRef: []config.ResourceReference{},
Scope: []string{"STEPS", "STAGES", "PARAMETERS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
{
Name: "testReportFilePath",
ResourceRef: []config.ResourceReference{},
Scope: []string{"STEPS", "STAGES", "PARAMETERS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
},
},
Containers: []config.Container{
{Image: "ppiper/container-structure-test", Options: []config.Option{{Name: "-u", Value: "0"}, {Name: "--entrypoint", Value: "''"}}},
},
},
}
return theMetaData
}

View File

@@ -0,0 +1,17 @@
package cmd
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestContainerExecuteStructureTestsCommand(t *testing.T) {
t.Parallel()
testCmd := ContainerExecuteStructureTestsCommand()
// only high level testing performed - details are tested in step generation procedure
assert.Equal(t, "containerExecuteStructureTests", testCmd.Use, "command name incorrect")
}

View File

@@ -0,0 +1,232 @@
package cmd
import (
"fmt"
"os"
"testing"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
type containerStructureTestsMockUtils struct {
shouldFail bool
requestedUrls []string
requestedFiles []string
*mock.FilesMock
*mock.ExecMockRunner
}
func (m *containerStructureTestsMockUtils) Glob(pattern string) (matches []string, err error) {
switch pattern {
case "**.yaml":
return []string{"config1.yaml", "config2.yaml"}, nil
case "empty":
return []string{}, nil
case "error":
return nil, errors.New("failed to find fies")
}
return nil, nil
}
func newContainerStructureTestsMockUtils() containerStructureTestsMockUtils {
utils := containerStructureTestsMockUtils{
shouldFail: false,
FilesMock: &mock.FilesMock{},
ExecMockRunner: &mock.ExecMockRunner{},
}
return utils
}
func TestRunContainerExecuteStructureTests(t *testing.T) {
t.Run("success case", func(t *testing.T) {
config := &containerExecuteStructureTestsOptions{
PullImage: true,
TestConfiguration: "**.yaml",
TestDriver: "docker",
TestImage: "reg/image:tag",
TestReportFilePath: "report.json",
}
mockUtils := newContainerStructureTestsMockUtils()
// test
err := runContainerExecuteStructureTests(config, &mockUtils)
// assert
expectedParams := []string{
"test",
"--config", "config1.yaml",
"--config", "config2.yaml",
"--driver", "docker",
"--pull",
"--image", "reg/image:tag",
"--test-report", "report.json",
}
assert.NoError(t, err)
if assert.Equal(t, 1, len(mockUtils.Calls)) {
assert.Equal(t, "container-structure-test", mockUtils.Calls[0].Exec)
assert.Equal(t, expectedParams, mockUtils.Calls[0].Params)
}
})
t.Run("success case - without pulling image", func(t *testing.T) {
config := &containerExecuteStructureTestsOptions{
TestConfiguration: "**.yaml",
TestDriver: "docker",
TestImage: "reg/image:tag",
TestReportFilePath: "report.json",
}
mockUtils := newContainerStructureTestsMockUtils()
// test
err := runContainerExecuteStructureTests(config, &mockUtils)
// assert
expectedParams := []string{
"test",
"--config", "config1.yaml",
"--config", "config2.yaml",
"--driver", "docker",
"--image", "reg/image:tag",
"--test-report", "report.json",
}
assert.NoError(t, err)
if assert.Equal(t, 1, len(mockUtils.Calls)) {
assert.Equal(t, "container-structure-test", mockUtils.Calls[0].Exec)
assert.Equal(t, expectedParams, mockUtils.Calls[0].Params)
}
})
t.Run("success case - verbose", func(t *testing.T) {
GeneralConfig.Verbose = true
config := &containerExecuteStructureTestsOptions{
TestConfiguration: "**.yaml",
TestDriver: "docker",
TestImage: "reg/image:tag",
TestReportFilePath: "report.json",
}
mockUtils := newContainerStructureTestsMockUtils()
// test
err := runContainerExecuteStructureTests(config, &mockUtils)
// assert
expectedParams := []string{
"test",
"--config", "config1.yaml",
"--config", "config2.yaml",
"--driver", "docker",
"--image", "reg/image:tag",
"--test-report", "report.json",
"--verbosity", "debug",
}
assert.NoError(t, err)
if assert.Equal(t, 1, len(mockUtils.Calls)) {
assert.Equal(t, "container-structure-test", mockUtils.Calls[0].Exec)
assert.Equal(t, expectedParams, mockUtils.Calls[0].Params)
}
GeneralConfig.Verbose = false
})
t.Run("success case - run on k8s", func(t *testing.T) {
if err := os.Setenv("ON_K8S", "true"); err != nil {
t.Error(err)
}
config := &containerExecuteStructureTestsOptions{
TestConfiguration: "**.yaml",
TestImage: "reg/image:tag",
TestReportFilePath: "report.json",
}
mockUtils := newContainerStructureTestsMockUtils()
// test
err := runContainerExecuteStructureTests(config, &mockUtils)
// assert
expectedParams := []string{
"test",
"--config", "config1.yaml",
"--config", "config2.yaml",
"--driver", "tar",
"--image", "reg/image:tag",
"--test-report", "report.json",
}
assert.NoError(t, err)
if assert.Equal(t, 1, len(mockUtils.Calls)) {
assert.Equal(t, "container-structure-test", mockUtils.Calls[0].Exec)
assert.Equal(t, expectedParams, mockUtils.Calls[0].Params)
}
os.Unsetenv("ON_K8S")
})
t.Run("error case - execution failed", func(t *testing.T) {
config := &containerExecuteStructureTestsOptions{
PullImage: true,
TestConfiguration: "**.yaml",
TestDriver: "docker",
TestImage: "reg/image:tag",
TestReportFilePath: "report.json",
}
mockUtils := newContainerStructureTestsMockUtils()
mockUtils.ExecMockRunner = &mock.ExecMockRunner{
ShouldFailOnCommand: map[string]error{"container-structure-test": fmt.Errorf("container-structure-test run failed")},
}
// test
err := runContainerExecuteStructureTests(config, &mockUtils)
// assert
assert.EqualError(t, err, "failed to run executable, command: '[container-structure-test test --config config1.yaml --config config2.yaml --driver docker --pull --image reg/image:tag --test-report report.json]', error: container-structure-test run failed: container-structure-test run failed")
})
t.Run("error case - configuration is missing", func(t *testing.T) {
config := &containerExecuteStructureTestsOptions{
PullImage: true,
TestConfiguration: "empty",
TestDriver: "docker",
TestReportFilePath: "report.json",
}
mockUtils := newContainerStructureTestsMockUtils()
// test
err := runContainerExecuteStructureTests(config, &mockUtils)
// assert
assert.EqualError(t, err, "config files mustn't be missing")
})
t.Run("error case - failed to find config files", func(t *testing.T) {
config := &containerExecuteStructureTestsOptions{
PullImage: true,
TestConfiguration: "error",
TestDriver: "docker",
TestReportFilePath: "report.json",
}
mockUtils := newContainerStructureTestsMockUtils()
// test
err := runContainerExecuteStructureTests(config, &mockUtils)
// assert
assert.EqualError(t, err, "failed to find config files, error: failed to find fies: failed to find fies")
})
t.Run("error case - incorrect driver type", func(t *testing.T) {
config := &containerExecuteStructureTestsOptions{
PullImage: true,
TestConfiguration: "**.yaml",
TestDriver: "wrongDriver",
TestReportFilePath: "report.json",
}
mockUtils := newContainerStructureTestsMockUtils()
// test
err := runContainerExecuteStructureTests(config, &mockUtils)
// assert
assert.EqualError(t, err, "test driver wrongDriver is incorrect. Possible drivers: docker, tar")
})
}

View File

@@ -29,6 +29,7 @@ func GetAllStepMetadata() map[string]config.StepData {
"cloudFoundryDeleteService": cloudFoundryDeleteServiceMetadata(),
"cloudFoundryDeleteSpace": cloudFoundryDeleteSpaceMetadata(),
"cloudFoundryDeploy": cloudFoundryDeployMetadata(),
"containerExecuteStructureTests": containerExecuteStructureTestsMetadata(),
"detectExecuteScan": detectExecuteScanMetadata(),
"fortifyExecuteScan": fortifyExecuteScanMetadata(),
"gctsCloneRepository": gctsCloneRepositoryMetadata(),

View File

@@ -136,6 +136,7 @@ func Execute() {
rootCmd.AddCommand(IntegrationArtifactDownloadCommand())
rootCmd.AddCommand(AbapEnvironmentAssembleConfirmCommand())
rootCmd.AddCommand(IntegrationArtifactUploadCommand())
rootCmd.AddCommand(ContainerExecuteStructureTestsCommand())
addRootFlags(rootCmd)
if err := rootCmd.Execute(); err != nil {

View File

@@ -226,15 +226,6 @@ steps:
deployTool: 'cf_native'
kaniko:
deployTool: 'cf_native'
containerExecuteStructureTests:
containerCommand: '/busybox/tail -f /dev/null'
containerShell: '/busybox/sh'
dockerImage: 'ppiper/container-structure-test'
dockerOptions: "-u 0 --entrypoint=''"
failOnError: true
stashContent:
- 'tests'
testReportFilePath: 'cst-report.json'
debugReportArchive:
shareConfidentialInformation: false
dockerExecute:

View File

@@ -0,0 +1,60 @@
metadata:
name: containerExecuteStructureTests
description: In this step [Container Structure Tests](https://github.com/GoogleContainerTools/container-structure-test) are executed.
longDescription: |
This testing framework allows you to execute different test types against a Docker container, for example:
- Command tests (only if a Docker Deamon is available)
- File existence tests
- File content tests
- Metadata test
spec:
inputs:
params:
- name: pullImage
type: bool
description: Force a pull of the tested image before running tests. Only relevant for testDriver 'docker'.
scope:
- STEPS
- STAGES
- PARAMETERS
- name: testConfiguration
type: string
description: Container structure test configuration in yml or json format. You can pass a pattern in order to execute multiple tests.
scope:
- STEPS
- STAGES
- PARAMETERS
mandatory: true
- name: testDriver
type: string
description: Container structure test driver to be used for testing, please see https://github.com/GoogleContainerTools/container-structure-test for details.
scope:
- STEPS
- STAGES
- PARAMETERS
- name: testImage
type: string
description: Image to be tested
scope:
- STEPS
- STAGES
- PARAMETERS
mandatory: true
- name: testReportFilePath
type: string
description: Path and name of the test report which will be generated
scope:
- STEPS
- STAGES
- PARAMETERS
default: cst-report.json
containers:
- image: ppiper/container-structure-test
command:
- /busybox/tail -f /dev/null
shell: /busybox/sh
options:
- name: -u
value: "0"
- name: --entrypoint
value: "''"

View File

@@ -181,6 +181,7 @@ public class CommonStepsTest extends BasePiperTest{
'integrationArtifactGetServiceEndpoint', //implementing new golang pattern without fields
'integrationArtifactDownload', //implementing new golang pattern without fields
'integrationArtifactUpload', //implementing new golang pattern without fields
'containerExecuteStructureTests', //implementing new golang pattern without fields
'transportRequestUploadSOLMAN', //implementing new golang pattern without fields
]

View File

@@ -1,156 +0,0 @@
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.rules.RuleChain
import util.*
import static org.hamcrest.Matchers.*
import static org.junit.Assert.assertThat
class ContainerExecuteStructureTestsTest extends BasePiperTest {
private ExpectedException thrown = ExpectedException.none()
private JenkinsStepRule jsr = new JenkinsStepRule(this)
private JenkinsLoggingRule jlr = new JenkinsLoggingRule(this)
private JenkinsShellCallRule jscr = new JenkinsShellCallRule(this)
private JenkinsDockerExecuteRule jedr = new JenkinsDockerExecuteRule(this)
@Rule
public RuleChain rules = Rules
.getCommonRules(this)
.around(new JenkinsReadYamlRule(this))
.around(thrown)
.around(jedr)
.around(jscr)
.around(jlr)
.around(jsr) // needs to be activated after jedr, otherwise executeDocker is not mocked
@Before
void init() throws Exception {
helper.registerAllowedMethod('stash', [String.class], null)
helper.registerAllowedMethod("findFiles", [Map.class], { map ->
def files
if(map.glob == 'notFound.json')
files = []
else if(map.glob == 'cst/*.yml')
files = [
new File("cst/test1.yml"),
new File("cst/test2.yml")
]
else
files = [new File(map.glob)]
return files.toArray()
})
}
@After
void cleanup() {
nullScript.env = [ON_K8S: null]
}
@Test
void testExecuteContainterStructureTestsDefault() throws Exception {
helper.registerAllowedMethod('readFile', [String.class], {s ->
return '{testResult: true}'
})
jsr.step.containerExecuteStructureTests(
script: nullScript,
juStabUtils: utils,
testConfiguration: 'cst/*.yml',
testImage: 'myRegistry/myImage:myTag'
)
// asserts
assertThat(jscr.shell, hasItem(allOf(
stringContainsInOrder(['#!/busybox/sh', 'container-structure-test', '--config']),
containsString("--config cst${File.separator}test1.yml"),
containsString("--config cst${File.separator}test2.yml"),
containsString('--driver docker'),
containsString('--image myRegistry/myImage:myTag'),
containsString('--test-report cst-report.json'),
)))
//currently no default Docker image
assertThat(jedr.dockerParams.dockerImage, is('ppiper/container-structure-test'))
assertThat(jedr.dockerParams.dockerOptions, is("-u 0 --entrypoint=''"))
assertThat(jedr.dockerParams.containerCommand, is('/busybox/tail -f /dev/null'))
assertThat(jedr.dockerParams.containerShell, is('/busybox/sh'))
assertThat(jlr.log, containsString('{testResult: true}'))
assertThat(jscr.shell, hasItem('docker pull myRegistry/myImage:myTag'))
}
@Test
void testExecuteContainterStructureTestsK8S() throws Exception {
def envDefault = nullScript.env
nullScript.env = [ON_K8S: 'true']
jsr.step.containerExecuteStructureTests(
script: nullScript,
juStabUtils: utils,
containerCommand: '/busybox/tail -f /dev/null',
containerShell: '/bin/sh',
dockerImage: 'myRegistry:55555/pathTo/myImage:myTag',
testConfiguration: 'cst/*.yml',
testImage: 'myRegistry/myImage:myTag'
)
nullScript.env = envDefault
// asserts
assertThat(jscr.shell, hasItem(allOf(
stringContainsInOrder(['#!/bin/sh', 'container-structure-test', '--config']),
containsString("--config cst${File.separator}test1.yml"),
containsString("--config cst${File.separator}test2.yml"),
containsString('--driver tar'),
containsString('--image myRegistry/myImage:myTag'),
containsString('--test-report cst-report.json'),
)))
assertThat(jedr.dockerParams.dockerImage, is('myRegistry:55555/pathTo/myImage:myTag'))
assertThat(jedr.dockerParams.containerCommand, is('/busybox/tail -f /dev/null'))
assertThat(jscr.shell, not(hasItem('docker pull myRegistry/myImage:myTag')))
}
@Test
void testExecuteContainterStructureTestsError() throws Exception {
helper.registerAllowedMethod('readFile', [String.class], {s ->
return '{testResult: true}'
})
helper.registerAllowedMethod('sh', [String.class], {s ->
if (s.startsWith('#!/busybox/sh\ncontainer-structure-test test')) {
throw new GroovyRuntimeException('shell call failed')
}
return null
})
thrown.expectMessage('ERROR: The execution of the container structure tests failed, see the log for details.')
jsr.step.containerExecuteStructureTests(
script: nullScript,
juStabUtils: utils,
containerCommand: '/busybox/tail -f /dev/null',
containerShell: '/busybox/sh',
testConfiguration: 'cst/*.yml',
testImage: 'myRegistry/myImage:myTag'
)
}
@Test
void testExecuteContainterStructureTestsErrorNoFailure() throws Exception {
helper.registerAllowedMethod('readFile', [String.class], {s ->
return '{testResult: true}'
})
helper.registerAllowedMethod('sh', [String.class], {s ->
if (s.startsWith('#!/busybox/sh\ncontainer-structure-test test')) {
throw new GroovyRuntimeException('shell call failed')
}
return null
})
jsr.step.containerExecuteStructureTests(
script: nullScript,
juStabUtils: utils,
containerCommand: '/busybox/tail -f /dev/null',
containerShell: '/busybox/sh',
failOnError: false,
testConfiguration: 'cst/*.yml',
testImage: 'myRegistry/myImage:myTag'
)
assertThat(jlr.log, containsString('Test execution failed'))
}
}

View File

@@ -1,143 +1,9 @@
import static com.sap.piper.Prerequisites.checkScript
import com.sap.piper.GenerateDocumentation
import com.sap.piper.ConfigurationHelper
import com.sap.piper.Utils
import groovy.transform.Field
@Field String STEP_NAME = getClass().getName()
@Field String METADATA_FILE = 'metadata/containerExecuteStructureTests.yaml'
@Field Set GENERAL_CONFIG_KEYS = [
/**
* Print more detailed information into the log.
* @possibleValues `true`, `false`
*/
'verbose'
]
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
/**
* @see dockerExecute
*/
'containerCommand',
/**
* @see dockerExecute
*/
'containerShell',
/**
* @see dockerExecute
*/
'dockerImage',
/**
* @see dockerExecute
*/
'dockerOptions',
/**
* @see dockerExecute
*/
'stashContent',
/**
* Defines the behavior, in case tests fail.
* @possibleValues `true`, `false`
*/
'failOnError',
/**
* Only relevant for testDriver 'docker'.
* @possibleValues `true`, `false`
*/
'pullImage',
/**
* Container structure test configuration in yml or json format. You can pass a pattern in order to execute multiple tests.
*/
'testConfiguration',
/**
* Container structure test driver to be used for testing, please see https://github.com/GoogleContainerTools/container-structure-test for details.
*/
'testDriver',
/**
* Image to be tested
*/
'testImage',
/**
* Path and name of the test report which will be generated
*/
'testReportFilePath',
])
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
/**
* In this step [Container Structure Tests](https://github.com/GoogleContainerTools/container-structure-test) are executed.
*
* This testing framework allows you to execute different test types against a Docker container, for example:
*
* * Command tests (only if a Docker Deamon is available)
* * File existence tests
* * File content tests
* * Metadata test
*/
@GenerateDocumentation
void call(Map parameters = [:]) {
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
def script = checkScript(this, parameters) ?: this
def utils = parameters.juStabUtils ?: new Utils()
String stageName = parameters.stageName ?: env.STAGE_NAME
// load default & individual configuration
Map config = ConfigurationHelper.newInstance(this)
.loadStepDefaults([:], stageName)
.mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
.mixinStageConfig(script.commonPipelineEnvironment, stageName, STEP_CONFIG_KEYS)
.mixin(parameters, PARAMETER_KEYS)
.addIfEmpty('testDriver', Boolean.valueOf(script.env.ON_K8S) ? 'tar' : 'docker')
.addIfNull('pullImage', !Boolean.valueOf(script.env.ON_K8S))
.withMandatoryProperty('dockerImage')
.use()
utils.pushToSWA([step: STEP_NAME], config)
config.stashContent = utils.unstashAll(config.stashContent)
List testConfig = findFiles(glob: config.testConfiguration)?.toList()
if (testConfig.isEmpty()) {
error "[${STEP_NAME}] No test description found with pattern '${config.testConfiguration}'"
} else {
echo "[${STEP_NAME}] Found files ${testConfig}"
}
def testConfigArgs = ''
testConfig.each {conf ->
testConfigArgs += "--config ${conf} "
}
//workaround for non-working '--pull' option in version 1.7.0 of container-structure-tests, see https://github.com/GoogleContainerTools/container-structure-test/issues/193
if (config.pullImage) {
if (config.verbose) echo "[${STEP_NAME}] Pulling image since configuration option pullImage is set to '${config.pullImage}'"
sh "docker pull ${config.testImage}"
}
try {
dockerExecute(
script: script,
containerCommand: config.containerCommand,
containerShell: config.containerShell,
dockerImage: config.dockerImage,
dockerOptions: config.dockerOptions,
stashContent: config.stashContent
) {
sh """#!${config.containerShell?:'/bin/sh'}
container-structure-test test ${testConfigArgs} --driver ${config.testDriver} --image ${config.testImage} --test-report ${config.testReportFilePath}${config.verbose ? ' --verbosity debug' : ''}"""
}
} catch (err) {
echo "[${STEP_NAME}] Test execution failed"
script.currentBuild.result = 'UNSTABLE'
if (config.failOnError) error "[${STEP_NAME}] ERROR: The execution of the container structure tests failed, see the log for details."
} finally {
echo "${readFile(config.testReportFilePath)}"
archiveArtifacts artifacts: config.testReportFilePath, allowEmptyArchive: true
}
}
List credentials = []
piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials)
}