You've already forked sap-jenkins-library
mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-07-13 01:30:24 +02:00
PythonBuild: Implementation of pythonBuild step (#3483)
* Implementation of pythonBuild step * minor update and refactoring * minor update * add integration test and test project to testdata dir * remove generated build data dir * Rewrite some logic. Minor fix in integration tests for python * Add new input parameters to pythonBuild.yaml * rewrite logic remove some checks * rollback * resolve merge conflict in piper.go Update logic in python build. Create bom now works fine * remove duplicate line * refactoring fix * resolve comment. Remove install build and change build command. Change twine upload command * add groovy wrapper for pythonBuild step * Rewrite tests. Remove some cheks from pythonBuild.go * add some test to pythonBuild_test.go * Add some parameters and credentials to the pythonBuild.groovy * fix issue in unit tests * add pythonBuild to fieldRelatedWhitelist * update integration test for pythonBuild * add imports * update integration tests and add a new one * minor fix * fix some issues in integration tests * update integration tests. Make it works again Co-authored-by: Anil Keshav <anil.keshav@sap.com> Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com>
This commit is contained in:
@ -88,6 +88,7 @@ func GetAllStepMetadata() map[string]config.StepData {
|
||||
"npmExecuteScripts": npmExecuteScriptsMetadata(),
|
||||
"pipelineCreateScanSummary": pipelineCreateScanSummaryMetadata(),
|
||||
"protecodeExecuteScan": protecodeExecuteScanMetadata(),
|
||||
"pythonBuild": pythonBuildMetadata(),
|
||||
"shellExecute": shellExecuteMetadata(),
|
||||
"sonarExecuteScan": sonarExecuteScanMetadata(),
|
||||
"terraformExecute": terraformExecuteMetadata(),
|
||||
|
@ -183,6 +183,7 @@ func Execute() {
|
||||
rootCmd.AddCommand(ApiProxyUploadCommand())
|
||||
rootCmd.AddCommand(GradleExecuteBuildCommand())
|
||||
rootCmd.AddCommand(ApiKeyValueMapUploadCommand())
|
||||
rootCmd.AddCommand(PythonBuildCommand())
|
||||
|
||||
addRootFlags(rootCmd)
|
||||
|
||||
|
107
cmd/pythonBuild.go
Normal file
107
cmd/pythonBuild.go
Normal file
@ -0,0 +1,107 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
PyBomFilename = "bom.xml"
|
||||
)
|
||||
|
||||
type pythonBuildUtils interface {
|
||||
command.ExecRunner
|
||||
FileExists(filename string) (bool, error)
|
||||
piperutils.FileUtils
|
||||
}
|
||||
|
||||
type pythonBuildUtilsBundle struct {
|
||||
*command.Command
|
||||
*piperutils.Files
|
||||
}
|
||||
|
||||
func newPythonBuildUtils() pythonBuildUtils {
|
||||
utils := pythonBuildUtilsBundle{
|
||||
Command: &command.Command{},
|
||||
Files: &piperutils.Files{},
|
||||
}
|
||||
// Reroute command output to logging framework
|
||||
utils.Stdout(log.Writer())
|
||||
utils.Stderr(log.Writer())
|
||||
return &utils
|
||||
}
|
||||
|
||||
func pythonBuild(config pythonBuildOptions, telemetryData *telemetry.CustomData) {
|
||||
utils := newPythonBuildUtils()
|
||||
|
||||
err := runPythonBuild(&config, telemetryData, utils)
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Fatal("step execution failed")
|
||||
}
|
||||
}
|
||||
|
||||
func runPythonBuild(config *pythonBuildOptions, telemetryData *telemetry.CustomData, utils pythonBuildUtils) error {
|
||||
|
||||
installFlags := []string{"-m", "pip", "install", "--upgrade"}
|
||||
|
||||
err := buildExecute(config, utils, installFlags)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Python build failed with error: %w", err)
|
||||
}
|
||||
|
||||
if config.CreateBOM {
|
||||
if err := runBOMCreationForPy(utils, installFlags); err != nil {
|
||||
return fmt.Errorf("BOM creation failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if config.Publish {
|
||||
if err := publishWithTwine(config, utils, installFlags); err != nil {
|
||||
return fmt.Errorf("failed to publish: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildExecute(config *pythonBuildOptions, utils pythonBuildUtils, installFlags []string) error {
|
||||
var flags []string
|
||||
flags = append(flags, config.BuildFlags...)
|
||||
flags = append(flags, "setup.py", "sdist", "bdist_wheel")
|
||||
|
||||
log.Entry().Info("starting building python project:")
|
||||
err := utils.RunExecutable("python3", flags...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runBOMCreationForPy(utils pythonBuildUtils, installFlags []string) error {
|
||||
installFlags = append(installFlags, "cyclonedx-bom")
|
||||
if err := utils.RunExecutable("python3", installFlags...); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := utils.RunExecutable("cyclonedx-bom", "--e", "--output", PyBomFilename); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func publishWithTwine(config *pythonBuildOptions, utils pythonBuildUtils, installFlags []string) error {
|
||||
installFlags = append(installFlags, "twine")
|
||||
if err := utils.RunExecutable("python3", installFlags...); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := utils.RunExecutable("twine", "upload", "--username", config.TargetRepositoryUser,
|
||||
"--password", config.TargetRepositoryPassword, "--repository-url", config.TargetRepositoryURL,
|
||||
"dist/*"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
215
cmd/pythonBuild_generated.go
Normal file
215
cmd/pythonBuild_generated.go
Normal file
@ -0,0 +1,215 @@
|
||||
// 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/splunk"
|
||||
"github.com/SAP/jenkins-library/pkg/telemetry"
|
||||
"github.com/SAP/jenkins-library/pkg/validation"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type pythonBuildOptions struct {
|
||||
BuildFlags []string `json:"buildFlags,omitempty"`
|
||||
CreateBOM bool `json:"createBOM,omitempty"`
|
||||
Publish bool `json:"publish,omitempty"`
|
||||
TargetRepositoryPassword string `json:"targetRepositoryPassword,omitempty"`
|
||||
TargetRepositoryUser string `json:"targetRepositoryUser,omitempty"`
|
||||
TargetRepositoryURL string `json:"targetRepositoryURL,omitempty"`
|
||||
}
|
||||
|
||||
// PythonBuildCommand Step build a python project
|
||||
func PythonBuildCommand() *cobra.Command {
|
||||
const STEP_NAME = "pythonBuild"
|
||||
|
||||
metadata := pythonBuildMetadata()
|
||||
var stepConfig pythonBuildOptions
|
||||
var startTime time.Time
|
||||
var logCollector *log.CollectorHook
|
||||
var splunkClient *splunk.Splunk
|
||||
telemetryClient := &telemetry.Telemetry{}
|
||||
|
||||
var createPythonBuildCmd = &cobra.Command{
|
||||
Use: STEP_NAME,
|
||||
Short: "Step build a python project",
|
||||
Long: `Step build python project with using test Vault credentials`,
|
||||
PreRunE: func(cmd *cobra.Command, _ []string) error {
|
||||
startTime = time.Now()
|
||||
log.SetStepName(STEP_NAME)
|
||||
log.SetVerbose(GeneralConfig.Verbose)
|
||||
|
||||
GeneralConfig.GitHubAccessTokens = ResolveAccessTokens(GeneralConfig.GitHubTokens)
|
||||
|
||||
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
|
||||
}
|
||||
log.RegisterSecret(stepConfig.TargetRepositoryPassword)
|
||||
log.RegisterSecret(stepConfig.TargetRepositoryUser)
|
||||
|
||||
if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 {
|
||||
sentryHook := log.NewSentryHook(GeneralConfig.HookConfig.SentryConfig.Dsn, GeneralConfig.CorrelationID)
|
||||
log.RegisterHook(&sentryHook)
|
||||
}
|
||||
|
||||
if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 {
|
||||
splunkClient = &splunk.Splunk{}
|
||||
logCollector = &log.CollectorHook{CorrelationID: GeneralConfig.CorrelationID}
|
||||
log.RegisterHook(logCollector)
|
||||
}
|
||||
|
||||
validation, err := validation.New(validation.WithJSONNamesForStructFields(), validation.WithPredefinedErrorMessages())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = validation.ValidateStruct(stepConfig); err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
stepTelemetryData := telemetry.CustomData{}
|
||||
stepTelemetryData.ErrorCode = "1"
|
||||
handler := func() {
|
||||
config.RemoveVaultSecretFiles()
|
||||
stepTelemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds())
|
||||
stepTelemetryData.ErrorCategory = log.GetErrorCategory().String()
|
||||
stepTelemetryData.PiperCommitHash = GitCommit
|
||||
telemetryClient.SetData(&stepTelemetryData)
|
||||
telemetryClient.Send()
|
||||
if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 {
|
||||
splunkClient.Send(telemetryClient.GetData(), logCollector)
|
||||
}
|
||||
}
|
||||
log.DeferExitHandler(handler)
|
||||
defer handler()
|
||||
telemetryClient.Initialize(GeneralConfig.NoTelemetry, STEP_NAME)
|
||||
if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 {
|
||||
splunkClient.Initialize(GeneralConfig.CorrelationID,
|
||||
GeneralConfig.HookConfig.SplunkConfig.Dsn,
|
||||
GeneralConfig.HookConfig.SplunkConfig.Token,
|
||||
GeneralConfig.HookConfig.SplunkConfig.Index,
|
||||
GeneralConfig.HookConfig.SplunkConfig.SendLogs)
|
||||
}
|
||||
pythonBuild(stepConfig, &stepTelemetryData)
|
||||
stepTelemetryData.ErrorCode = "0"
|
||||
log.Entry().Info("SUCCESS")
|
||||
},
|
||||
}
|
||||
|
||||
addPythonBuildFlags(createPythonBuildCmd, &stepConfig)
|
||||
return createPythonBuildCmd
|
||||
}
|
||||
|
||||
func addPythonBuildFlags(cmd *cobra.Command, stepConfig *pythonBuildOptions) {
|
||||
cmd.Flags().StringSliceVar(&stepConfig.BuildFlags, "buildFlags", []string{}, "Defines list of build flags to be used.")
|
||||
cmd.Flags().BoolVar(&stepConfig.CreateBOM, "createBOM", false, "Creates the bill of materials (BOM) using CycloneDX plugin.")
|
||||
cmd.Flags().BoolVar(&stepConfig.Publish, "publish", false, "Configures the build to publish artifacts to a repository.")
|
||||
cmd.Flags().StringVar(&stepConfig.TargetRepositoryPassword, "targetRepositoryPassword", os.Getenv("PIPER_targetRepositoryPassword"), "Password for the target repository where the compiled binaries shall be uploaded - typically provided by the CI/CD environment.")
|
||||
cmd.Flags().StringVar(&stepConfig.TargetRepositoryUser, "targetRepositoryUser", os.Getenv("PIPER_targetRepositoryUser"), "Username for the target repository where the compiled binaries shall be uploaded - typically provided by the CI/CD environment.")
|
||||
cmd.Flags().StringVar(&stepConfig.TargetRepositoryURL, "targetRepositoryURL", os.Getenv("PIPER_targetRepositoryURL"), "URL of the target repository where the compiled binaries shall be uploaded - typically provided by the CI/CD environment.")
|
||||
|
||||
}
|
||||
|
||||
// retrieve step metadata
|
||||
func pythonBuildMetadata() config.StepData {
|
||||
var theMetaData = config.StepData{
|
||||
Metadata: config.StepMetadata{
|
||||
Name: "pythonBuild",
|
||||
Aliases: []config.Alias{},
|
||||
Description: "Step build a python project",
|
||||
},
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{
|
||||
Name: "buildFlags",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "[]string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
Default: []string{},
|
||||
},
|
||||
{
|
||||
Name: "createBOM",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"GENERAL", "STEPS", "STAGES", "PARAMETERS"},
|
||||
Type: "bool",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
Default: false,
|
||||
},
|
||||
{
|
||||
Name: "publish",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"STEPS", "STAGES", "PARAMETERS"},
|
||||
Type: "bool",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
Default: false,
|
||||
},
|
||||
{
|
||||
Name: "targetRepositoryPassword",
|
||||
ResourceRef: []config.ResourceReference{
|
||||
{
|
||||
Name: "commonPipelineEnvironment",
|
||||
Param: "custom/repositoryPassword",
|
||||
},
|
||||
},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
Default: os.Getenv("PIPER_targetRepositoryPassword"),
|
||||
},
|
||||
{
|
||||
Name: "targetRepositoryUser",
|
||||
ResourceRef: []config.ResourceReference{
|
||||
{
|
||||
Name: "commonPipelineEnvironment",
|
||||
Param: "custom/repositoryUsername",
|
||||
},
|
||||
},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
Default: os.Getenv("PIPER_targetRepositoryUser"),
|
||||
},
|
||||
{
|
||||
Name: "targetRepositoryURL",
|
||||
ResourceRef: []config.ResourceReference{
|
||||
{
|
||||
Name: "commonPipelineEnvironment",
|
||||
Param: "custom/repositoryUrl",
|
||||
},
|
||||
},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
Default: os.Getenv("PIPER_targetRepositoryURL"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Containers: []config.Container{
|
||||
{Name: "python", Image: "python:3.9", WorkingDir: "/home/node"},
|
||||
},
|
||||
},
|
||||
}
|
||||
return theMetaData
|
||||
}
|
17
cmd/pythonBuild_generated_test.go
Normal file
17
cmd/pythonBuild_generated_test.go
Normal file
@ -0,0 +1,17 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPythonBuildCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCmd := PythonBuildCommand()
|
||||
|
||||
// only high level testing performed - details are tested in step generation procedure
|
||||
assert.Equal(t, "pythonBuild", testCmd.Use, "command name incorrect")
|
||||
|
||||
}
|
127
cmd/pythonBuild_test.go
Normal file
127
cmd/pythonBuild_test.go
Normal file
@ -0,0 +1,127 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
piperhttp "github.com/SAP/jenkins-library/pkg/http"
|
||||
"github.com/SAP/jenkins-library/pkg/telemetry"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/mock"
|
||||
)
|
||||
|
||||
type pythonBuildMockUtils struct {
|
||||
t *testing.T
|
||||
config *pythonBuildOptions
|
||||
*mock.ExecMockRunner
|
||||
*mock.FilesMock
|
||||
}
|
||||
|
||||
type puthonBuildMockUtils struct {
|
||||
*mock.ExecMockRunner
|
||||
*mock.FilesMock
|
||||
|
||||
clientOptions []piperhttp.ClientOptions // set by mock
|
||||
fileUploads map[string]string // set by mock
|
||||
}
|
||||
|
||||
func newPythonBuildTestsUtils() pythonBuildMockUtils {
|
||||
utils := pythonBuildMockUtils{
|
||||
ExecMockRunner: &mock.ExecMockRunner{},
|
||||
FilesMock: &mock.FilesMock{},
|
||||
}
|
||||
return utils
|
||||
}
|
||||
|
||||
func (f *pythonBuildMockUtils) GetConfig() *pythonBuildOptions {
|
||||
return f.config
|
||||
}
|
||||
|
||||
func TestRunPythonBuild(t *testing.T) {
|
||||
|
||||
t.Run("success - build", func(t *testing.T) {
|
||||
config := pythonBuildOptions{}
|
||||
utils := newPythonBuildTestsUtils()
|
||||
telemetryData := telemetry.CustomData{}
|
||||
|
||||
err := runPythonBuild(&config, &telemetryData, utils)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "python3", utils.ExecMockRunner.Calls[0].Exec)
|
||||
assert.Equal(t, []string{"setup.py", "sdist", "bdist_wheel"}, utils.ExecMockRunner.Calls[0].Params)
|
||||
})
|
||||
|
||||
t.Run("failure - build failure", func(t *testing.T) {
|
||||
config := pythonBuildOptions{}
|
||||
utils := newPythonBuildTestsUtils()
|
||||
utils.ShouldFailOnCommand = map[string]error{"python3 setup.py sdist bdist_wheel": fmt.Errorf("build failure")}
|
||||
telemetryData := telemetry.CustomData{}
|
||||
|
||||
err := runPythonBuild(&config, &telemetryData, utils)
|
||||
assert.EqualError(t, err, "Python build failed with error: build failure")
|
||||
})
|
||||
|
||||
t.Run("success - publishes binaries", func(t *testing.T) {
|
||||
config := pythonBuildOptions{
|
||||
Publish: true,
|
||||
TargetRepositoryURL: "https://my.target.repository.local",
|
||||
TargetRepositoryUser: "user",
|
||||
TargetRepositoryPassword: "password",
|
||||
}
|
||||
utils := newPythonBuildTestsUtils()
|
||||
telemetryData := telemetry.CustomData{}
|
||||
|
||||
err := runPythonBuild(&config, &telemetryData, utils)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "python3", utils.ExecMockRunner.Calls[0].Exec)
|
||||
assert.Equal(t, []string{"setup.py", "sdist", "bdist_wheel"}, utils.ExecMockRunner.Calls[0].Params)
|
||||
assert.Equal(t, "python3", utils.ExecMockRunner.Calls[1].Exec)
|
||||
assert.Equal(t, []string{"-m", "pip", "install", "--upgrade", "twine"}, utils.ExecMockRunner.Calls[1].Params)
|
||||
assert.Equal(t, "twine", utils.ExecMockRunner.Calls[2].Exec)
|
||||
assert.Equal(t, []string{"upload", "--username", config.TargetRepositoryUser,
|
||||
"--password", config.TargetRepositoryPassword, "--repository-url", config.TargetRepositoryURL,
|
||||
"dist/*"}, utils.ExecMockRunner.Calls[2].Params)
|
||||
})
|
||||
|
||||
t.Run("success - create BOM", func(t *testing.T) {
|
||||
config := pythonBuildOptions{
|
||||
CreateBOM: true,
|
||||
}
|
||||
utils := newPythonBuildTestsUtils()
|
||||
telemetryData := telemetry.CustomData{}
|
||||
|
||||
err := runPythonBuild(&config, &telemetryData, utils)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "python3", utils.ExecMockRunner.Calls[0].Exec)
|
||||
assert.Equal(t, []string{"setup.py", "sdist", "bdist_wheel"}, utils.ExecMockRunner.Calls[0].Params)
|
||||
assert.Equal(t, "python3", utils.ExecMockRunner.Calls[1].Exec)
|
||||
assert.Equal(t, []string{"-m", "pip", "install", "--upgrade", "cyclonedx-bom"}, utils.ExecMockRunner.Calls[1].Params)
|
||||
assert.Equal(t, "cyclonedx-bom", utils.ExecMockRunner.Calls[2].Exec)
|
||||
assert.Equal(t, []string{"--e", "--output", "bom.xml"}, utils.ExecMockRunner.Calls[2].Params)
|
||||
})
|
||||
|
||||
t.Run("failure - install pre-requisites for BOM creation", func(t *testing.T) {
|
||||
config := pythonBuildOptions{
|
||||
CreateBOM: true,
|
||||
}
|
||||
utils := newPythonBuildTestsUtils()
|
||||
utils.ShouldFailOnCommand = map[string]error{"python3 -m pip install --upgrade cyclonedx-bom": fmt.Errorf("install failure")}
|
||||
telemetryData := telemetry.CustomData{}
|
||||
|
||||
err := runPythonBuild(&config, &telemetryData, utils)
|
||||
assert.EqualError(t, err, "BOM creation failed: install failure")
|
||||
})
|
||||
|
||||
t.Run("failure - install pre-requisites for Twine upload", func(t *testing.T) {
|
||||
config := pythonBuildOptions{
|
||||
Publish: true,
|
||||
}
|
||||
utils := newPythonBuildTestsUtils()
|
||||
utils.ShouldFailOnCommand = map[string]error{"python3 -m pip install --upgrade twine": fmt.Errorf("install failure")}
|
||||
telemetryData := telemetry.CustomData{}
|
||||
|
||||
err := runPythonBuild(&config, &telemetryData, utils)
|
||||
assert.EqualError(t, err, "failed to publish: install failure")
|
||||
})
|
||||
}
|
89
integration/integration_python_build_test.go
Normal file
89
integration/integration_python_build_test.go
Normal file
@ -0,0 +1,89 @@
|
||||
//go:build integration
|
||||
// +build integration
|
||||
|
||||
// can be execute with go test -tags=integration ./integration/...
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
)
|
||||
|
||||
func TestBuildPythonProject(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
pwd, err := os.Getwd()
|
||||
assert.NoError(t, err, "Getting current working directory failed.")
|
||||
pwd = filepath.Dir(pwd)
|
||||
|
||||
tempDir, err := createTmpDir("")
|
||||
defer os.RemoveAll(tempDir) // clean up
|
||||
assert.NoError(t, err, "Error when creating temp dir")
|
||||
|
||||
err = copyDir(filepath.Join(pwd, "integration", "testdata", "TestPythonIntegration", "python-project"), tempDir)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to copy test project.")
|
||||
}
|
||||
|
||||
//workaround to use test script util it is possible to set workdir for Exec call
|
||||
testScript := fmt.Sprintf(`#!/bin/sh
|
||||
cd /test
|
||||
/piperbin/piper pythonBuild >test-log.txt 2>&1`)
|
||||
ioutil.WriteFile(filepath.Join(tempDir, "runPiper.sh"), []byte(testScript), 0700)
|
||||
|
||||
reqNode := testcontainers.ContainerRequest{
|
||||
Image: "python:3.9",
|
||||
Cmd: []string{"tail", "-f"},
|
||||
BindMounts: map[string]string{
|
||||
pwd: "/piperbin",
|
||||
tempDir: "/test",
|
||||
},
|
||||
}
|
||||
|
||||
nodeContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
||||
ContainerRequest: reqNode,
|
||||
Started: true,
|
||||
})
|
||||
|
||||
code, err := nodeContainer.Exec(ctx, []string{"sh", "/test/runPiper.sh"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, code)
|
||||
|
||||
content, err := ioutil.ReadFile(filepath.Join(tempDir, "/test-log.txt"))
|
||||
if err != nil {
|
||||
t.Fatal("Could not read test-log.txt.", err)
|
||||
}
|
||||
output := string(content)
|
||||
|
||||
assert.Contains(t, output, "info pythonBuild - running command: python3 setup.py sdist bdist_wheel")
|
||||
assert.Contains(t, output, "info pythonBuild - running command: python3 -m pip install --upgrade cyclonedx-bom")
|
||||
assert.Contains(t, output, "info pythonBuild - running command: cyclonedx-bom --e --output bom.xml")
|
||||
assert.Contains(t, output, "info pythonBuild - SUCCESS")
|
||||
|
||||
//workaround to use test script util it is possible to set workdir for Exec call
|
||||
testScript = fmt.Sprintf(`#!/bin/sh
|
||||
cd /test
|
||||
ls -l . dist build >files-list.txt 2>&1`)
|
||||
ioutil.WriteFile(filepath.Join(tempDir, "runPiper.sh"), []byte(testScript), 0700)
|
||||
|
||||
code, err = nodeContainer.Exec(ctx, []string{"sh", "/test/runPiper.sh"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, code)
|
||||
|
||||
content, err = ioutil.ReadFile(filepath.Join(tempDir, "/files-list.txt"))
|
||||
if err != nil {
|
||||
t.Fatal("Could not read files-list.txt.", err)
|
||||
}
|
||||
output = string(content)
|
||||
assert.Contains(t, output, "bom.xml")
|
||||
assert.Contains(t, output, "example-pkg-0.0.1.tar.gz")
|
||||
assert.Contains(t, output, "example_pkg-0.0.1-py3-none-any.whl")
|
||||
}
|
6
integration/testdata/TestPythonIntegration/python-project/.pipeline/config.yml
vendored
Normal file
6
integration/testdata/TestPythonIntegration/python-project/.pipeline/config.yml
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
general:
|
||||
|
||||
steps:
|
||||
pythonBuild:
|
||||
createBOM: true
|
||||
publish: false
|
19
integration/testdata/TestPythonIntegration/python-project/LICENSE
vendored
Normal file
19
integration/testdata/TestPythonIntegration/python-project/LICENSE
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2018 The Python Packaging Authority
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
5
integration/testdata/TestPythonIntegration/python-project/README.md
vendored
Normal file
5
integration/testdata/TestPythonIntegration/python-project/README.md
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
# Example Package
|
||||
|
||||
This is a simple example package. You can use
|
||||
[Github-flavored Markdown](https://guides.github.com/features/mastering-markdown/)
|
||||
to write your content.
|
6
integration/testdata/TestPythonIntegration/python-project/pyproject.toml
vendored
Normal file
6
integration/testdata/TestPythonIntegration/python-project/pyproject.toml
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
[build-system]
|
||||
requires = [
|
||||
"setuptools>=42",
|
||||
"wheel"
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
24
integration/testdata/TestPythonIntegration/python-project/setup.cfg
vendored
Normal file
24
integration/testdata/TestPythonIntegration/python-project/setup.cfg
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
[metadata]
|
||||
name = example-package-TEST
|
||||
version = 0.0.1
|
||||
author = Example Author
|
||||
author_email = author@example.com
|
||||
description = A small example package
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
url = https://github.com/pypa/sampleproject
|
||||
project_urls =
|
||||
Bug Tracker = https://github.com/pypa/sampleproject/issues
|
||||
classifiers =
|
||||
Programming Language :: Python :: 3
|
||||
License :: OSI Approved :: MIT License
|
||||
Operating System :: OS Independent
|
||||
|
||||
[options]
|
||||
package_dir =
|
||||
= src
|
||||
packages = find:
|
||||
python_requires = >=3.6
|
||||
|
||||
[options.packages.find]
|
||||
where = src
|
19
integration/testdata/TestPythonIntegration/python-project/setup.py
vendored
Normal file
19
integration/testdata/TestPythonIntegration/python-project/setup.py
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
name="example-pkg",
|
||||
version="0.0.1",
|
||||
author="Example Author",
|
||||
author_email="author@example.com",
|
||||
description="A small example package",
|
||||
long_description="Long description for small example package",
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://github.com/example/pypi/github",
|
||||
packages=setuptools.find_packages(),
|
||||
classifiers=[
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
],
|
||||
python_requires='>=3.6',
|
||||
)
|
0
integration/testdata/TestPythonIntegration/python-project/src/example_package/__init__.py
vendored
Normal file
0
integration/testdata/TestPythonIntegration/python-project/src/example_package/__init__.py
vendored
Normal file
2
integration/testdata/TestPythonIntegration/python-project/src/example_package/example.py
vendored
Normal file
2
integration/testdata/TestPythonIntegration/python-project/src/example_package/example.py
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
def add_one(number):
|
||||
return number + 1
|
16
integration/testdata/TestPythonIntegration/python-project/src/example_pkg.egg-info/PKG-INFO
vendored
Normal file
16
integration/testdata/TestPythonIntegration/python-project/src/example_pkg.egg-info/PKG-INFO
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: example-pkg
|
||||
Version: 0.0.1
|
||||
Summary: A small example package
|
||||
Home-page: https://github.com/example/pypi/github
|
||||
Author: Example Author
|
||||
Author-email: author@example.com
|
||||
License: UNKNOWN
|
||||
Project-URL: Bug Tracker, https://github.com/pypa/sampleproject/issues
|
||||
Description: Long description for small example package
|
||||
Platform: UNKNOWN
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Requires-Python: >=3.6
|
||||
Description-Content-Type: text/markdown
|
10
integration/testdata/TestPythonIntegration/python-project/src/example_pkg.egg-info/SOURCES.txt
vendored
Normal file
10
integration/testdata/TestPythonIntegration/python-project/src/example_pkg.egg-info/SOURCES.txt
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
README.md
|
||||
pyproject.toml
|
||||
setup.cfg
|
||||
setup.py
|
||||
src/example_package/__init__.py
|
||||
src/example_package/example.py
|
||||
src/example_pkg.egg-info/PKG-INFO
|
||||
src/example_pkg.egg-info/SOURCES.txt
|
||||
src/example_pkg.egg-info/dependency_links.txt
|
||||
src/example_pkg.egg-info/top_level.txt
|
@ -0,0 +1 @@
|
||||
|
@ -0,0 +1 @@
|
||||
example_package
|
66
resources/metadata/pythonBuild.yaml
Normal file
66
resources/metadata/pythonBuild.yaml
Normal file
@ -0,0 +1,66 @@
|
||||
metadata:
|
||||
name: pythonBuild
|
||||
description: Step build a python project
|
||||
longDescription: Step build python project with using test Vault credentials
|
||||
spec:
|
||||
inputs:
|
||||
params:
|
||||
- name: buildFlags
|
||||
type: "[]string"
|
||||
description: Defines list of build flags to be used.
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- name: createBOM
|
||||
type: bool
|
||||
description: Creates the bill of materials (BOM) using CycloneDX plugin.
|
||||
scope:
|
||||
- GENERAL
|
||||
- STEPS
|
||||
- STAGES
|
||||
- PARAMETERS
|
||||
default: false
|
||||
- name: publish
|
||||
type: bool
|
||||
description: Configures the build to publish artifacts to a repository.
|
||||
scope:
|
||||
- STEPS
|
||||
- STAGES
|
||||
- PARAMETERS
|
||||
- name: targetRepositoryPassword
|
||||
description: "Password for the target repository where the compiled binaries shall be uploaded - typically provided by the CI/CD environment."
|
||||
type: string
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
secret: true
|
||||
resourceRef:
|
||||
- name: commonPipelineEnvironment
|
||||
param: custom/repositoryPassword
|
||||
- name: targetRepositoryUser
|
||||
description: "Username for the target repository where the compiled binaries shall be uploaded - typically provided by the CI/CD environment."
|
||||
type: string
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
secret: true
|
||||
resourceRef:
|
||||
- name: commonPipelineEnvironment
|
||||
param: custom/repositoryUsername
|
||||
- name: targetRepositoryURL
|
||||
description: "URL of the target repository where the compiled binaries shall be uploaded - typically provided by the CI/CD environment."
|
||||
type: string
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
resourceRef:
|
||||
- name: commonPipelineEnvironment
|
||||
param: custom/repositoryUrl
|
||||
containers:
|
||||
- name: python
|
||||
image: python:3.9
|
||||
workingDir: /home/node
|
@ -213,6 +213,7 @@ public class CommonStepsTest extends BasePiperTest{
|
||||
'gradleExecuteBuild', //implementing new golang pattern without fields
|
||||
'shellExecute', //implementing new golang pattern without fields
|
||||
'apiKeyValueMapUpload', //implementing new golang pattern without fields
|
||||
'pythonBuild', //implementing new golang pattern without fields
|
||||
]
|
||||
|
||||
@Test
|
||||
|
16
vars/pythonBuild.groovy
Normal file
16
vars/pythonBuild.groovy
Normal file
@ -0,0 +1,16 @@
|
||||
import com.sap.piper.BuildTool
|
||||
import com.sap.piper.DownloadCacheUtils
|
||||
import groovy.transform.Field
|
||||
|
||||
import static com.sap.piper.Prerequisites.checkScript
|
||||
|
||||
@Field String METADATA_FILE = 'metadata/pythonBuild.yaml'
|
||||
@Field String STEP_NAME = getClass().getName()
|
||||
|
||||
void call(Map parameters = [:]) {
|
||||
List credentials = [[type: 'token', id: 'altDeploymentRepositoryPasswordId', env: ['PIPER_altDeploymentRepositoryPassword']]]
|
||||
final script = checkScript(this, parameters) ?: this
|
||||
parameters = DownloadCacheUtils.injectDownloadCacheInParameters(script, parameters, BuildTool.PIP)
|
||||
|
||||
piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials)
|
||||
}
|
Reference in New Issue
Block a user