You've already forked sap-jenkins-library
							
							
				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:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							dfe9cb6149
						
					
				
				
					commit
					33699c7388
				
			
							
								
								
									
										93
									
								
								cmd/containerExecuteStructureTests.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								cmd/containerExecuteStructureTests.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
							
								
								
									
										154
									
								
								cmd/containerExecuteStructureTests_generated.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								cmd/containerExecuteStructureTests_generated.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
							
								
								
									
										17
									
								
								cmd/containerExecuteStructureTests_generated_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								cmd/containerExecuteStructureTests_generated_test.go
									
									
									
									
									
										Normal 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") | ||||
|  | ||||
| } | ||||
							
								
								
									
										232
									
								
								cmd/containerExecuteStructureTests_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										232
									
								
								cmd/containerExecuteStructureTests_test.go
									
									
									
									
									
										Normal 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") | ||||
| 	}) | ||||
| } | ||||
| @@ -29,6 +29,7 @@ func GetAllStepMetadata() map[string]config.StepData { | ||||
| 		"cloudFoundryDeleteService":               cloudFoundryDeleteServiceMetadata(), | ||||
| 		"cloudFoundryDeleteSpace":                 cloudFoundryDeleteSpaceMetadata(), | ||||
| 		"cloudFoundryDeploy":                      cloudFoundryDeployMetadata(), | ||||
| 		"containerExecuteStructureTests":          containerExecuteStructureTestsMetadata(), | ||||
| 		"detectExecuteScan":                       detectExecuteScanMetadata(), | ||||
| 		"fortifyExecuteScan":                      fortifyExecuteScanMetadata(), | ||||
| 		"gctsCloneRepository":                     gctsCloneRepositoryMetadata(), | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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: | ||||
|   | ||||
							
								
								
									
										60
									
								
								resources/metadata/containerExecuteStructureTests.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								resources/metadata/containerExecuteStructureTests.yaml
									
									
									
									
									
										Normal 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: "''" | ||||
| @@ -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 | ||||
|     ] | ||||
|  | ||||
|   | ||||
| @@ -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')) | ||||
|     } | ||||
| } | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user