You've already forked sap-jenkins-library
mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-10-08 23:02:10 +02:00
refactor(python): create python package (#5482)
This commit is contained in:
committed by
GitHub
parent
de63042799
commit
6740963b68
@@ -2,20 +2,19 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/buildsettings"
|
||||
"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/python"
|
||||
"github.com/SAP/jenkins-library/pkg/telemetry"
|
||||
)
|
||||
|
||||
const (
|
||||
PyBomFilename = "bom-pip.xml"
|
||||
stepName = "pythonBuild"
|
||||
cycloneDxPackageVersion = "cyclonedx-bom==6.1.1"
|
||||
cycloneDxSchemaVersion = "1.4"
|
||||
cycloneDxVersion = "6.1.1"
|
||||
cycloneDxSchemaVersion = "1.4"
|
||||
stepName = "pythonBuild"
|
||||
)
|
||||
|
||||
type pythonBuildUtils interface {
|
||||
@@ -32,7 +31,7 @@ type pythonBuildUtilsBundle struct {
|
||||
func newPythonBuildUtils() pythonBuildUtils {
|
||||
utils := pythonBuildUtilsBundle{
|
||||
Command: &command.Command{
|
||||
StepName: "pythonBuild",
|
||||
StepName: stepName,
|
||||
},
|
||||
Files: &piperutils.Files{},
|
||||
}
|
||||
@@ -52,30 +51,49 @@ func pythonBuild(config pythonBuildOptions, telemetryData *telemetry.CustomData,
|
||||
}
|
||||
|
||||
func runPythonBuild(config *pythonBuildOptions, telemetryData *telemetry.CustomData, utils pythonBuildUtils, commonPipelineEnvironment *pythonBuildCommonPipelineEnvironment) error {
|
||||
pipInstallFlags := []string{"install", "--upgrade"}
|
||||
virutalEnvironmentPathMap := make(map[string]string)
|
||||
if exitHandler, err := python.CreateVirtualEnvironment(utils.RunExecutable, utils.RemoveAll, config.VirtualEnvironmentName); err != nil {
|
||||
return err
|
||||
} else {
|
||||
log.DeferExitHandler(exitHandler)
|
||||
defer exitHandler()
|
||||
}
|
||||
|
||||
err := createVirtualEnvironment(utils, config, virutalEnvironmentPathMap)
|
||||
if err != nil {
|
||||
if err := python.BuildWithSetupPy(utils.RunExecutable, config.VirtualEnvironmentName, config.BuildFlags, config.SetupFlags); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = buildExecute(config, utils, pipInstallFlags, virutalEnvironmentPathMap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Python build failed with error: %w", err)
|
||||
}
|
||||
|
||||
if config.CreateBOM {
|
||||
if err := runBOMCreationForPy(utils, pipInstallFlags, virutalEnvironmentPathMap, config); err != nil {
|
||||
if err := python.CreateBOM(utils.RunExecutable, utils.FileExists, config.VirtualEnvironmentName, config.RequirementsFilePath, cycloneDxVersion, cycloneDxSchemaVersion); err != nil {
|
||||
return fmt.Errorf("BOM creation failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Entry().Debugf("creating build settings information...")
|
||||
if info, err := createBuildSettingsInfo(config); err != nil {
|
||||
return err
|
||||
} else {
|
||||
commonPipelineEnvironment.custom.buildSettingsInfo = info
|
||||
}
|
||||
|
||||
if config.Publish {
|
||||
if err := python.PublishPackage(
|
||||
utils.RunExecutable,
|
||||
config.VirtualEnvironmentName,
|
||||
config.TargetRepositoryURL,
|
||||
config.TargetRepositoryUser,
|
||||
config.TargetRepositoryPassword,
|
||||
); err != nil {
|
||||
return fmt.Errorf("failed to publish: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: extract to common place
|
||||
func createBuildSettingsInfo(config *pythonBuildOptions) (string, error) {
|
||||
log.Entry().Debugf("creating build settings information...")
|
||||
dockerImage, err := GetDockerImageValue(stepName)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
pythonConfig := buildsettings.BuildOptions{
|
||||
@@ -88,98 +106,5 @@ func runPythonBuild(config *pythonBuildOptions, telemetryData *telemetry.CustomD
|
||||
if err != nil {
|
||||
log.Entry().Warnf("failed to create build settings info: %v", err)
|
||||
}
|
||||
commonPipelineEnvironment.custom.buildSettingsInfo = buildSettingsInfo
|
||||
|
||||
if config.Publish {
|
||||
if err := publishWithTwine(config, utils, pipInstallFlags, virutalEnvironmentPathMap); err != nil {
|
||||
return fmt.Errorf("failed to publish: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = removeVirtualEnvironment(utils, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildExecute(config *pythonBuildOptions, utils pythonBuildUtils, pipInstallFlags []string, virutalEnvironmentPathMap map[string]string) error {
|
||||
var flags []string
|
||||
flags = append(flags, config.BuildFlags...)
|
||||
flags = append(flags, "setup.py")
|
||||
flags = append(flags, config.SetupFlags...)
|
||||
flags = append(flags, "sdist", "bdist_wheel")
|
||||
|
||||
log.Entry().Info("starting building python project:")
|
||||
err := utils.RunExecutable(virutalEnvironmentPathMap["python"], flags...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createVirtualEnvironment(utils pythonBuildUtils, config *pythonBuildOptions, virutalEnvironmentPathMap map[string]string) error {
|
||||
virtualEnvironmentFlags := []string{"-m", "venv", config.VirtualEnvironmentName}
|
||||
err := utils.RunExecutable("python3", virtualEnvironmentFlags...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = utils.RunExecutable("bash", "-c", "source "+filepath.Join(config.VirtualEnvironmentName, "bin", "activate"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
virutalEnvironmentPathMap["pip"] = filepath.Join(config.VirtualEnvironmentName, "bin", "pip")
|
||||
// venv will create symlinks to python3 inside the container
|
||||
virutalEnvironmentPathMap["python"] = "python"
|
||||
virutalEnvironmentPathMap["deactivate"] = filepath.Join(config.VirtualEnvironmentName, "bin", "deactivate")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeVirtualEnvironment(utils pythonBuildUtils, config *pythonBuildOptions) error {
|
||||
err := utils.RemoveAll(config.VirtualEnvironmentName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runBOMCreationForPy(utils pythonBuildUtils, pipInstallFlags []string, virutalEnvironmentPathMap map[string]string, config *pythonBuildOptions) error {
|
||||
pipInstallOriginalFlags := pipInstallFlags
|
||||
exists, _ := utils.FileExists(config.RequirementsFilePath)
|
||||
if exists {
|
||||
pipInstallRequirementsFlags := append(pipInstallOriginalFlags, "--requirement", config.RequirementsFilePath)
|
||||
if err := utils.RunExecutable(virutalEnvironmentPathMap["pip"], pipInstallRequirementsFlags...); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
log.Entry().Warnf("unable to find requirements.txt file at %s , continuing SBOM generation without requirements.txt", config.RequirementsFilePath)
|
||||
}
|
||||
|
||||
pipInstallCycloneDxFlags := append(pipInstallOriginalFlags, cycloneDxPackageVersion)
|
||||
|
||||
if err := utils.RunExecutable(virutalEnvironmentPathMap["pip"], pipInstallCycloneDxFlags...); err != nil {
|
||||
return err
|
||||
}
|
||||
virutalEnvironmentPathMap["cyclonedx"] = filepath.Join(config.VirtualEnvironmentName, "bin", "cyclonedx-py")
|
||||
|
||||
if err := utils.RunExecutable(virutalEnvironmentPathMap["cyclonedx"], "env", "--output-file", PyBomFilename, "--output-format", "XML", "--spec-version", cycloneDxSchemaVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func publishWithTwine(config *pythonBuildOptions, utils pythonBuildUtils, pipInstallFlags []string, virutalEnvironmentPathMap map[string]string) error {
|
||||
pipInstallFlags = append(pipInstallFlags, "twine")
|
||||
if err := utils.RunExecutable(virutalEnvironmentPathMap["pip"], pipInstallFlags...); err != nil {
|
||||
return err
|
||||
}
|
||||
virutalEnvironmentPathMap["twine"] = filepath.Join(config.VirtualEnvironmentName, "bin", "twine")
|
||||
if err := utils.RunExecutable(virutalEnvironmentPathMap["twine"], "upload", "--username", config.TargetRepositoryUser,
|
||||
"--password", config.TargetRepositoryPassword, "--repository-url", config.TargetRepositoryURL, "--disable-progress-bar",
|
||||
"dist/*"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return buildSettingsInfo, nil
|
||||
}
|
||||
|
@@ -53,7 +53,7 @@ func TestRunPythonBuild(t *testing.T) {
|
||||
telemetryData := telemetry.CustomData{}
|
||||
|
||||
err := runPythonBuild(&config, &telemetryData, utils, &cpe)
|
||||
assert.EqualError(t, err, "Python build failed with error: build failure")
|
||||
assert.EqualError(t, err, "failed to build package: build failure")
|
||||
})
|
||||
|
||||
t.Run("success - publishes binaries", func(t *testing.T) {
|
||||
@@ -72,14 +72,16 @@ func TestRunPythonBuild(t *testing.T) {
|
||||
assert.Equal(t, []string{"-m", "venv", config.VirtualEnvironmentName}, utils.ExecMockRunner.Calls[0].Params)
|
||||
assert.Equal(t, "bash", utils.ExecMockRunner.Calls[1].Exec)
|
||||
assert.Equal(t, []string{"-c", "source " + filepath.Join("dummy", "bin", "activate")}, utils.ExecMockRunner.Calls[1].Params)
|
||||
assert.Equal(t, "python", utils.ExecMockRunner.Calls[2].Exec)
|
||||
assert.Equal(t, []string{"setup.py", "sdist", "bdist_wheel"}, utils.ExecMockRunner.Calls[2].Params)
|
||||
assert.Equal(t, filepath.Join("dummy", "bin", "pip"), utils.ExecMockRunner.Calls[3].Exec)
|
||||
assert.Equal(t, []string{"install", "--upgrade", "twine"}, utils.ExecMockRunner.Calls[3].Params)
|
||||
assert.Equal(t, filepath.Join("dummy", "bin", "twine"), utils.ExecMockRunner.Calls[4].Exec)
|
||||
assert.Equal(t, "dummy/bin/pip", utils.ExecMockRunner.Calls[2].Exec)
|
||||
assert.Equal(t, []string{"install", "--upgrade", "--root-user-action=ignore", "wheel"}, utils.ExecMockRunner.Calls[2].Params)
|
||||
assert.Equal(t, "dummy/bin/python", utils.ExecMockRunner.Calls[3].Exec)
|
||||
assert.Equal(t, []string{"setup.py", "sdist", "bdist_wheel"}, utils.ExecMockRunner.Calls[3].Params)
|
||||
assert.Equal(t, filepath.Join("dummy", "bin", "pip"), utils.ExecMockRunner.Calls[4].Exec)
|
||||
assert.Equal(t, []string{"install", "--upgrade", "--root-user-action=ignore", "twine"}, utils.ExecMockRunner.Calls[4].Params)
|
||||
assert.Equal(t, filepath.Join("dummy", "bin", "twine"), utils.ExecMockRunner.Calls[5].Exec)
|
||||
assert.Equal(t, []string{"upload", "--username", config.TargetRepositoryUser,
|
||||
"--password", config.TargetRepositoryPassword, "--repository-url", config.TargetRepositoryURL,
|
||||
"--disable-progress-bar", "dist/*"}, utils.ExecMockRunner.Calls[4].Params)
|
||||
"--disable-progress-bar", "dist/*"}, utils.ExecMockRunner.Calls[5].Params)
|
||||
})
|
||||
|
||||
t.Run("success - create BOM", func(t *testing.T) {
|
||||
@@ -97,42 +99,13 @@ func TestRunPythonBuild(t *testing.T) {
|
||||
assert.Equal(t, []string{"-m", "venv", config.VirtualEnvironmentName}, utils.ExecMockRunner.Calls[0].Params)
|
||||
assert.Equal(t, "bash", utils.ExecMockRunner.Calls[1].Exec)
|
||||
assert.Equal(t, []string{"-c", "source " + filepath.Join("dummy", "bin", "activate")}, utils.ExecMockRunner.Calls[1].Params)
|
||||
assert.Equal(t, "python", utils.ExecMockRunner.Calls[2].Exec)
|
||||
assert.Equal(t, []string{"setup.py", "sdist", "bdist_wheel"}, utils.ExecMockRunner.Calls[2].Params)
|
||||
assert.Equal(t, filepath.Join("dummy", "bin", "pip"), utils.ExecMockRunner.Calls[3].Exec)
|
||||
assert.Equal(t, []string{"install", "--upgrade", "cyclonedx-bom==6.1.1"}, utils.ExecMockRunner.Calls[3].Params)
|
||||
assert.Equal(t, filepath.Join("dummy", "bin", "cyclonedx-py"), utils.ExecMockRunner.Calls[4].Exec)
|
||||
assert.Equal(t, []string{"env", "--output-file", "bom-pip.xml", "--output-format", "XML", "--spec-version", "1.4"}, utils.ExecMockRunner.Calls[4].Params)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPythonBuildExecute(t *testing.T) {
|
||||
t.Run("Test build with flags", func(t *testing.T) {
|
||||
config := pythonBuildOptions{
|
||||
BuildFlags: []string{"--verbose"},
|
||||
SetupFlags: []string{"egg_info", "--tag-build=pr13"},
|
||||
VirtualEnvironmentName: "venv",
|
||||
}
|
||||
|
||||
utils := pythonBuildMockUtils{
|
||||
ExecMockRunner: &mock.ExecMockRunner{},
|
||||
}
|
||||
|
||||
virutalEnvironmentPathMap := map[string]string{
|
||||
"python": "python",
|
||||
}
|
||||
|
||||
err := buildExecute(&config, &utils, []string{}, virutalEnvironmentPathMap)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "python", utils.ExecMockRunner.Calls[0].Exec)
|
||||
assert.Equal(t, []string{
|
||||
"--verbose",
|
||||
"setup.py",
|
||||
"egg_info",
|
||||
"--tag-build=pr13",
|
||||
"sdist",
|
||||
"bdist_wheel",
|
||||
}, utils.ExecMockRunner.Calls[0].Params)
|
||||
assert.Equal(t, "dummy/bin/pip", utils.ExecMockRunner.Calls[2].Exec)
|
||||
assert.Equal(t, []string{"install", "--upgrade", "--root-user-action=ignore", "wheel"}, utils.ExecMockRunner.Calls[2].Params)
|
||||
assert.Equal(t, "dummy/bin/python", utils.ExecMockRunner.Calls[3].Exec)
|
||||
assert.Equal(t, []string{"setup.py", "sdist", "bdist_wheel"}, utils.ExecMockRunner.Calls[3].Params)
|
||||
assert.Equal(t, filepath.Join("dummy", "bin", "pip"), utils.ExecMockRunner.Calls[4].Exec)
|
||||
assert.Equal(t, []string{"install", "--upgrade", "--root-user-action=ignore", "cyclonedx-bom==6.1.1"}, utils.ExecMockRunner.Calls[4].Params)
|
||||
assert.Equal(t, filepath.Join("dummy", "bin", "cyclonedx-py"), utils.ExecMockRunner.Calls[5].Exec)
|
||||
assert.Equal(t, []string{"env", "--output-file", "bom-pip.xml", "--output-format", "XML", "--spec-version", "1.4"}, utils.ExecMockRunner.Calls[5].Params)
|
||||
})
|
||||
}
|
||||
|
@@ -64,8 +64,8 @@ func TestPythonIntegrationBuildProject(t *testing.T) {
|
||||
}
|
||||
output := string(content)
|
||||
|
||||
assert.Contains(t, output, "info pythonBuild - running command: python setup.py sdist bdist_wheel")
|
||||
assert.Contains(t, output, "info pythonBuild - running command: piperBuild-env/bin/pip install --upgrade cyclonedx-bom")
|
||||
assert.Contains(t, output, "info pythonBuild - running command: piperBuild-env/bin/python setup.py sdist bdist_wheel")
|
||||
assert.Contains(t, output, "info pythonBuild - running command: piperBuild-env/bin/pip install --upgrade --root-user-action=ignore cyclonedx-bom==")
|
||||
assert.Contains(t, output, "info pythonBuild - running command: piperBuild-env/bin/cyclonedx-py env --output-file bom-pip.xml")
|
||||
assert.Contains(t, output, "info pythonBuild - SUCCESS")
|
||||
|
||||
|
49
pkg/python/bom.go
Normal file
49
pkg/python/bom.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package python
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
)
|
||||
|
||||
const (
|
||||
BOMFilename = "bom-pip.xml"
|
||||
)
|
||||
|
||||
func CreateBOM(
|
||||
executeFn func(executable string, params ...string) error,
|
||||
existsFn func(path string) (bool, error),
|
||||
virtualEnv string,
|
||||
requirementsFile string,
|
||||
cycloneDxVersion string,
|
||||
cycloneDxSchemaVersion string,
|
||||
) error {
|
||||
if exists, _ := existsFn(requirementsFile); exists {
|
||||
if err := InstallRequirements(executeFn, virtualEnv, requirementsFile); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
log.Entry().Warnf("unable to find requirements.txt file at %s , continuing SBOM generation without requirements.txt", requirementsFile)
|
||||
}
|
||||
|
||||
if err := InstallCycloneDX(executeFn, virtualEnv, cycloneDxVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cycloneDxBinary := "cyclonedx-py"
|
||||
if len(virtualEnv) > 0 {
|
||||
cycloneDxBinary = filepath.Join(virtualEnv, "bin", cycloneDxBinary)
|
||||
}
|
||||
|
||||
log.Entry().Debug("creating BOM")
|
||||
if err := executeFn(cycloneDxBinary,
|
||||
"env",
|
||||
"--output-file", BOMFilename,
|
||||
"--output-format", "XML",
|
||||
"--spec-version", cycloneDxSchemaVersion,
|
||||
); err != nil {
|
||||
return fmt.Errorf("failed to create BOM: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
36
pkg/python/bom_test.go
Normal file
36
pkg/python/bom_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
//go:build unit
|
||||
// +build unit
|
||||
|
||||
package python
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCreateBOM(t *testing.T) {
|
||||
// init
|
||||
mockRunner := mock.ExecMockRunner{}
|
||||
mockFiles := mock.FilesMock{}
|
||||
|
||||
// test
|
||||
err := CreateBOM(mockRunner.RunExecutable, mockFiles.FileExists, ".venv", "requirements.txt", "1.2.3", "16")
|
||||
|
||||
// assert
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, mockRunner.Calls, 2)
|
||||
assert.Equal(t, ".venv/bin/pip", mockRunner.Calls[0].Exec)
|
||||
assert.Equal(t, []string{
|
||||
"install",
|
||||
"--upgrade",
|
||||
"--root-user-action=ignore",
|
||||
"cyclonedx-bom==1.2.3"}, mockRunner.Calls[0].Params)
|
||||
assert.Equal(t, ".venv/bin/cyclonedx-py", mockRunner.Calls[1].Exec)
|
||||
assert.Equal(t, []string{
|
||||
"env",
|
||||
"--output-file", "bom-pip.xml",
|
||||
"--output-format", "XML",
|
||||
"--spec-version", "16"}, mockRunner.Calls[1].Params)
|
||||
}
|
37
pkg/python/build.go
Normal file
37
pkg/python/build.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package python
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
)
|
||||
|
||||
func BuildWithSetupPy(
|
||||
executeFn func(executable string, params ...string) error,
|
||||
virtualEnv string,
|
||||
pythonArgs []string,
|
||||
setupArgs []string,
|
||||
) error {
|
||||
// install dependency
|
||||
if err := InstallWheel(executeFn, virtualEnv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pythonBinary := "python"
|
||||
if len(virtualEnv) > 0 {
|
||||
pythonBinary = filepath.Join(virtualEnv, "bin", pythonBinary)
|
||||
}
|
||||
|
||||
var flags []string
|
||||
flags = append(flags, pythonArgs...)
|
||||
flags = append(flags, "setup.py")
|
||||
flags = append(flags, setupArgs...)
|
||||
flags = append(flags, "sdist", "bdist_wheel")
|
||||
|
||||
log.Entry().Debug("building project")
|
||||
if err := executeFn(pythonBinary, flags...); err != nil {
|
||||
return fmt.Errorf("failed to build package: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
40
pkg/python/build_test.go
Normal file
40
pkg/python/build_test.go
Normal file
@@ -0,0 +1,40 @@
|
||||
//go:build unit
|
||||
// +build unit
|
||||
|
||||
package python
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBuildWithSetupPy(t *testing.T) {
|
||||
// init
|
||||
mockRunner := mock.ExecMockRunner{}
|
||||
buildFlags := []string{"--verbose"}
|
||||
setupFlags := []string{"egg_info", "--tag-build=pr13"}
|
||||
|
||||
// test
|
||||
err := BuildWithSetupPy(mockRunner.RunExecutable, ".venv", buildFlags, setupFlags)
|
||||
|
||||
// assert
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, mockRunner.Calls, 2)
|
||||
assert.Equal(t, ".venv/bin/pip", mockRunner.Calls[0].Exec)
|
||||
assert.Equal(t, []string{
|
||||
"install",
|
||||
"--upgrade",
|
||||
"--root-user-action=ignore",
|
||||
"wheel"}, mockRunner.Calls[0].Params)
|
||||
assert.Equal(t, ".venv/bin/python", mockRunner.Calls[1].Exec)
|
||||
assert.Equal(t, []string{
|
||||
"--verbose",
|
||||
"setup.py",
|
||||
"egg_info",
|
||||
"--tag-build=pr13",
|
||||
"sdist",
|
||||
"bdist_wheel",
|
||||
}, mockRunner.Calls[1].Params)
|
||||
}
|
29
pkg/python/env.go
Normal file
29
pkg/python/env.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package python
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
)
|
||||
|
||||
func CreateVirtualEnvironment(
|
||||
executeFn func(executable string, params ...string) error,
|
||||
removeFn func(executable string) error,
|
||||
virtualEnv string,
|
||||
) (func(), error) {
|
||||
exitHandler := func() {
|
||||
if err := removeFn(virtualEnv); err != nil {
|
||||
log.Entry().Debugf("failed to remove virtual environment %s: %v", virtualEnv, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation for creating a virtual environment
|
||||
if err := executeFn("python3", "-m", "venv", virtualEnv); err != nil {
|
||||
return exitHandler, fmt.Errorf("failed to create virtual environment %s: %w", virtualEnv, err)
|
||||
}
|
||||
if err := executeFn("bash", "-c", fmt.Sprintf("source %s", filepath.Join(virtualEnv, "bin", "activate"))); err != nil {
|
||||
return exitHandler, fmt.Errorf("failed to activate virtual environment %s: %w", virtualEnv, err)
|
||||
}
|
||||
return exitHandler, nil
|
||||
}
|
28
pkg/python/env_test.go
Normal file
28
pkg/python/env_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
//go:build unit
|
||||
// +build unit
|
||||
|
||||
package python
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCreateVirtualEnvironment(t *testing.T) {
|
||||
// init
|
||||
mockRunner := mock.ExecMockRunner{}
|
||||
mockFiles := mock.FilesMock{}
|
||||
|
||||
// test
|
||||
_, err := CreateVirtualEnvironment(mockRunner.RunExecutable, mockFiles.RemoveAll, ".venv")
|
||||
|
||||
// assert
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, mockRunner.Calls, 2)
|
||||
assert.Equal(t, "python3", mockRunner.Calls[0].Exec)
|
||||
assert.Equal(t, []string{"-m", "venv", ".venv"}, mockRunner.Calls[0].Params)
|
||||
assert.Equal(t, "bash", mockRunner.Calls[1].Exec)
|
||||
assert.Equal(t, []string{"-c", "source .venv/bin/activate"}, mockRunner.Calls[1].Params)
|
||||
}
|
77
pkg/python/pip.go
Normal file
77
pkg/python/pip.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package python
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
)
|
||||
|
||||
var (
|
||||
PipInstallFlags = []string{"install", "--upgrade", "--root-user-action=ignore"}
|
||||
)
|
||||
|
||||
func Install(
|
||||
executeFn func(executable string, params ...string) error,
|
||||
virtualEnv string,
|
||||
module string,
|
||||
version string,
|
||||
extraArgs []string,
|
||||
) error {
|
||||
pipBinary := "pip"
|
||||
if len(virtualEnv) > 0 {
|
||||
pipBinary = filepath.Join(virtualEnv, "bin", pipBinary)
|
||||
}
|
||||
|
||||
flags := PipInstallFlags
|
||||
// flags := append([]string{"-m", "pip"}, PipInstallFlags...)
|
||||
|
||||
if len(extraArgs) > 0 {
|
||||
flags = append(flags, extraArgs...)
|
||||
}
|
||||
if len(version) > 0 {
|
||||
module = fmt.Sprintf("%s==%s", module, version)
|
||||
}
|
||||
if len(module) > 0 {
|
||||
flags = append(flags, module)
|
||||
}
|
||||
|
||||
if err := executeFn(pipBinary, flags...); err != nil {
|
||||
return fmt.Errorf("failed to install %s: %w", module, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func InstallRequirements(
|
||||
executeFn func(executable string, params ...string) error,
|
||||
virtualEnv string,
|
||||
requirementsFile string,
|
||||
) error {
|
||||
log.Entry().Debug("installing requirements")
|
||||
return Install(executeFn, virtualEnv, "", "", []string{"--requirement", requirementsFile})
|
||||
}
|
||||
|
||||
func InstallWheel(
|
||||
executeFn func(executable string, params ...string) error,
|
||||
virtualEnv string,
|
||||
) error {
|
||||
log.Entry().Debug("installing wheel")
|
||||
return Install(executeFn, virtualEnv, "wheel", "", nil)
|
||||
}
|
||||
|
||||
func InstallTwine(
|
||||
executeFn func(executable string, params ...string) error,
|
||||
virtualEnv string,
|
||||
) error {
|
||||
log.Entry().Debug("installing twine")
|
||||
return Install(executeFn, virtualEnv, "twine", "", nil)
|
||||
}
|
||||
|
||||
func InstallCycloneDX(
|
||||
executeFn func(executable string, params ...string) error,
|
||||
virtualEnv string,
|
||||
cycloneDXVersion string,
|
||||
) error {
|
||||
log.Entry().Debug("installing cyclonedx-bom")
|
||||
return Install(executeFn, virtualEnv, "cyclonedx-bom", cycloneDXVersion, nil)
|
||||
}
|
82
pkg/python/pip_test.go
Normal file
82
pkg/python/pip_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
//go:build unit
|
||||
// +build unit
|
||||
|
||||
package python
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestInstallRequirements(t *testing.T) {
|
||||
// init
|
||||
mockRunner := mock.ExecMockRunner{}
|
||||
|
||||
// test
|
||||
err := InstallRequirements(mockRunner.RunExecutable, "", "requirements.txt")
|
||||
|
||||
// assert
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "pip", mockRunner.Calls[0].Exec)
|
||||
assert.Equal(t, []string{
|
||||
"install",
|
||||
"--upgrade",
|
||||
"--root-user-action=ignore",
|
||||
"--requirement", "requirements.txt"}, mockRunner.Calls[0].Params)
|
||||
}
|
||||
|
||||
func TestInstallWheel(t *testing.T) {
|
||||
// init
|
||||
mockRunner := mock.ExecMockRunner{}
|
||||
|
||||
// test
|
||||
err := InstallWheel(mockRunner.RunExecutable, "")
|
||||
|
||||
// assert
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, mockRunner.Calls, 1)
|
||||
assert.Equal(t, "pip", mockRunner.Calls[0].Exec)
|
||||
assert.Equal(t, []string{
|
||||
"install",
|
||||
"--upgrade",
|
||||
"--root-user-action=ignore",
|
||||
"wheel"}, mockRunner.Calls[0].Params)
|
||||
}
|
||||
|
||||
func TestInstallTwine(t *testing.T) {
|
||||
// init
|
||||
mockRunner := mock.ExecMockRunner{}
|
||||
|
||||
// test
|
||||
err := InstallTwine(mockRunner.RunExecutable, "")
|
||||
|
||||
// assert
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, mockRunner.Calls, 1)
|
||||
assert.Equal(t, "pip", mockRunner.Calls[0].Exec)
|
||||
assert.Equal(t, []string{
|
||||
"install",
|
||||
"--upgrade",
|
||||
"--root-user-action=ignore",
|
||||
"twine"}, mockRunner.Calls[0].Params)
|
||||
}
|
||||
|
||||
func TestInstallCycloneDXWithVersion(t *testing.T) {
|
||||
// init
|
||||
mockRunner := mock.ExecMockRunner{}
|
||||
|
||||
// test
|
||||
err := InstallCycloneDX(mockRunner.RunExecutable, "", "1.0.0")
|
||||
|
||||
// assert
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, mockRunner.Calls, 1)
|
||||
assert.Equal(t, "pip", mockRunner.Calls[0].Exec)
|
||||
assert.Equal(t, []string{
|
||||
"install",
|
||||
"--upgrade",
|
||||
"--root-user-action=ignore",
|
||||
"cyclonedx-bom==1.0.0"}, mockRunner.Calls[0].Params)
|
||||
}
|
33
pkg/python/publish.go
Normal file
33
pkg/python/publish.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package python
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func PublishPackage(
|
||||
executeFn func(executable string, params ...string) error,
|
||||
virtualEnv string,
|
||||
repository string,
|
||||
username string,
|
||||
password string,
|
||||
) error {
|
||||
// install dependency
|
||||
if err := InstallTwine(executeFn, virtualEnv); err != nil {
|
||||
return err
|
||||
}
|
||||
// handle virtual environment
|
||||
twineBinary := "twine"
|
||||
if len(virtualEnv) > 0 {
|
||||
twineBinary = filepath.Join(virtualEnv, "bin", twineBinary)
|
||||
}
|
||||
// publish project
|
||||
return executeFn(
|
||||
twineBinary,
|
||||
"upload",
|
||||
"--username", username,
|
||||
"--password", password,
|
||||
"--repository-url", repository,
|
||||
"--disable-progress-bar",
|
||||
"dist/*",
|
||||
)
|
||||
}
|
36
pkg/python/publish_test.go
Normal file
36
pkg/python/publish_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
//go:build unit
|
||||
// +build unit
|
||||
|
||||
package python
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPublishWithVirtualEnv(t *testing.T) {
|
||||
// init
|
||||
mockRunner := mock.ExecMockRunner{}
|
||||
|
||||
// test
|
||||
err := PublishPackage(mockRunner.RunExecutable, ".venv", "repository", "anything", "anything")
|
||||
|
||||
// assert
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ".venv/bin/pip", mockRunner.Calls[0].Exec)
|
||||
assert.Equal(t, []string{
|
||||
"install",
|
||||
"--upgrade",
|
||||
"--root-user-action=ignore",
|
||||
"twine"}, mockRunner.Calls[0].Params)
|
||||
assert.Equal(t, ".venv/bin/twine", mockRunner.Calls[1].Exec)
|
||||
assert.Equal(t, []string{
|
||||
"upload",
|
||||
"--username", "anything",
|
||||
"--password", "anything",
|
||||
"--repository-url", "repository",
|
||||
"--disable-progress-bar",
|
||||
"dist/*"}, mockRunner.Calls[1].Params)
|
||||
}
|
Reference in New Issue
Block a user