Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cnbBuild): support builders with different CNB user ids #4625

Merged
merged 1 commit into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 33 additions & 10 deletions cmd/cnbBuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"path"
"path/filepath"
"syscall"

"github.com/SAP/jenkins-library/pkg/buildpacks"
"github.com/SAP/jenkins-library/pkg/buildsettings"
Expand Down Expand Up @@ -285,8 +286,14 @@ func (config *cnbBuildOptions) resolvePath(utils cnbutils.BuildUtils) (buildpack

func callCnbBuild(config *cnbBuildOptions, telemetryData *telemetry.CustomData, utils cnbutils.BuildUtils, commonPipelineEnvironment *cnbBuildCommonPipelineEnvironment, httpClient piperhttp.Sender) error {
stepName := "cnbBuild"
telemetry := buildpacks.NewTelemetry(telemetryData)

err := isBuilder(utils)
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.Wrap(err, "the provided dockerImage is not a valid builder")
}

telemetry := buildpacks.NewTelemetry(telemetryData)
dockerImage, err := GetDockerImageValue(stepName)
if err != nil {
log.Entry().Warnf("failed to retrieve dockerImage configuration: '%v'", err)
Expand Down Expand Up @@ -362,24 +369,29 @@ func runCnbBuild(config *cnbBuildOptions, telemetry *buildpacks.Telemetry, image
return errors.Wrap(err, fmt.Sprintf("failed to clean up platform folder %s", platformPath))
}

tempdir, err := os.MkdirTemp("", "cnbBuild-")
tempdir, err := utils.TempDir("", "cnbBuild-")
if err != nil {
return errors.Wrap(err, "failed to create tempdir")
}
defer os.RemoveAll(tempdir)
defer utils.RemoveAll(tempdir)

uid, gid, err := cnbutils.CnbUserInfo()
if err != nil {
return errors.Wrap(err, "failed to get user information")
}

err = utils.Chown(tempdir, uid, gid)
if err != nil {
return errors.Wrap(err, "failed to change tempdir ownership")
}

if config.BuildEnvVars == nil {
config.BuildEnvVars = map[string]interface{}{}
}
config.BuildEnvVars["TMPDIR"] = tempdir

telemetrySegment := createInitialTelemetrySegment(config, utils)

err = isBuilder(utils)
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.Wrap(err, "the provided dockerImage is not a valid builder")
}

include := ignore.CompileIgnoreLines("**/*")
exclude := ignore.CompileIgnoreLines("piper", ".pipeline", ".git")

Expand Down Expand Up @@ -486,6 +498,10 @@ func runCnbBuild(config *cnbBuildOptions, telemetry *buildpacks.Telemetry, image
}
}

if err := utils.Chown(target, uid, gid); err != nil {
return err
}

if ok, _ := utils.FileExists(filepath.Join(target, "pom.xml")); ok {
err = linkTargetFolder(utils, source, target)
if err != nil {
Expand Down Expand Up @@ -565,7 +581,14 @@ func runCnbBuild(config *cnbBuildOptions, telemetry *buildpacks.Telemetry, image
}

creatorArgs = append(creatorArgs, fmt.Sprintf("%s:%s", containerImage, targetImage.ContainerImageTag))
err = utils.RunExecutable(creatorPath, creatorArgs...)
attr := &syscall.SysProcAttr{
Credential: &syscall.Credential{
Uid: uint32(uid),
pbusko marked this conversation as resolved.
Show resolved Hide resolved
Gid: uint32(gid),
},
}

err = utils.RunExecutableWithAttrs(creatorPath, attr, creatorArgs...)
if err != nil {
log.SetErrorCategory(log.ErrorBuild)
return errors.Wrapf(err, "execution of '%s' failed", creatorArgs)
Expand Down
2 changes: 1 addition & 1 deletion cmd/cnbBuild_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions cmd/cnbBuild_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ func assetBuildEnv(t *testing.T, utils cnbutils.MockUtils, key, value string) bo
func TestRunCnbBuild(t *testing.T) {
configOptions.OpenFile = piperconf.OpenPiperFile

t.Setenv("CNB_USER_ID", "1000")
t.Setenv("CNB_GROUP_ID", "1000")

t.Run("prefers direct configuration", func(t *testing.T) {
t.Parallel()
commonPipelineEnvironment := cnbBuildCommonPipelineEnvironment{}
Expand Down
26 changes: 13 additions & 13 deletions integration/integration_cnb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func TestCNBIntegrationNPMProject(t *testing.T) {

container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: baseBuilder,
User: "cnb",
User: "0",
TestDir: []string{"testdata", "TestCnbIntegration"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
Environment: map[string]string{
Expand All @@ -53,7 +53,7 @@ func TestCNBIntegrationNPMProject(t *testing.T) {

container2 := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: baseBuilder,
User: "cnb",
User: "0",
TestDir: []string{"testdata", "TestCnbIntegration"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
Environment: map[string]string{
Expand Down Expand Up @@ -93,7 +93,7 @@ func TestCNBIntegrationProjectDescriptor(t *testing.T) {

container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: baseBuilder,
User: "cnb",
User: "0",
TestDir: []string{"testdata", "TestCnbIntegration", "project"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
})
Expand Down Expand Up @@ -123,7 +123,7 @@ func TestCNBIntegrationBuildSummary(t *testing.T) {

container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: baseBuilder,
User: "cnb",
User: "0",
TestDir: []string{"testdata", "TestCnbIntegration", "project"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
})
Expand All @@ -148,7 +148,7 @@ func TestCNBIntegrationZipPath(t *testing.T) {

container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: baseBuilder,
User: "cnb",
User: "0",
TestDir: []string{"testdata", "TestCnbIntegration", "zip"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
})
Expand Down Expand Up @@ -177,7 +177,7 @@ func TestCNBIntegrationNonZipPath(t *testing.T) {

container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: baseBuilder,
User: "cnb",
User: "0",
TestDir: []string{"testdata", "TestMtaIntegration", "npm"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
})
Expand All @@ -197,7 +197,7 @@ func TestCNBIntegrationNPMCustomBuildpacksFullProject(t *testing.T) {

container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: baseBuilder,
User: "cnb",
User: "0",
TestDir: []string{"testdata", "TestMtaIntegration", "npm"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
})
Expand Down Expand Up @@ -225,7 +225,7 @@ func TestCNBIntegrationNPMCustomBuildpacksBuildpacklessProject(t *testing.T) {

container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: "paketobuildpacks/builder:buildpackless-full",
User: "cnb",
User: "0",
TestDir: []string{"testdata", "TestMtaIntegration", "npm"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
})
Expand Down Expand Up @@ -266,7 +266,7 @@ func TestCNBIntegrationBindings(t *testing.T) {

container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: baseBuilder,
User: "cnb",
User: "0",
TestDir: []string{"testdata", "TestCnbIntegration"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
Environment: map[string]string{
Expand Down Expand Up @@ -294,7 +294,7 @@ func TestCNBIntegrationMultiImage(t *testing.T) {

container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: baseBuilder,
User: "cnb",
User: "0",
TestDir: []string{"testdata", "TestCnbIntegration"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
})
Expand Down Expand Up @@ -328,7 +328,7 @@ func TestCNBIntegrationPreserveFiles(t *testing.T) {

container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: baseBuilder,
User: "cnb",
User: "0",
TestDir: []string{"testdata", "TestCnbIntegration"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
})
Expand All @@ -348,7 +348,7 @@ func TestCNBIntegrationPreserveFilesIgnored(t *testing.T) {

container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: baseBuilder,
User: "cnb",
User: "0",
TestDir: []string{"testdata", "TestCnbIntegration"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
})
Expand All @@ -367,7 +367,7 @@ func TestCNBIntegrationPrePostBuildpacks(t *testing.T) {

container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: baseBuilder,
User: "cnb",
User: "0",
TestDir: []string{"testdata", "TestCnbIntegration"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
Environment: map[string]string{
Expand Down
32 changes: 32 additions & 0 deletions pkg/cnbutils/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package cnbutils

import (
"os"
"strconv"

"github.com/pkg/errors"
)

func CnbUserInfo() (int, int, error) {
uidStr, ok := os.LookupEnv("CNB_USER_ID")
if !ok {
return 0, 0, errors.New("environment variable CNB_USER_ID not found")
}

gidStr, ok := os.LookupEnv("CNB_GROUP_ID")
if !ok {
return 0, 0, errors.New("environment variable CNB_GROUP_ID not found")
}
pbusko marked this conversation as resolved.
Show resolved Hide resolved

uid, err := strconv.Atoi(uidStr)
if err != nil {
return 0, 0, err
}

gid, err := strconv.Atoi(gidStr)
if err != nil {
return 0, 0, err
}

return uid, gid, nil
}
10 changes: 10 additions & 0 deletions pkg/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type runner interface {
type ExecRunner interface {
runner
RunExecutable(executable string, params ...string) error
RunExecutableWithAttrs(executable string, sysProcAttr *syscall.SysProcAttr, params ...string) error
RunExecutableInBackground(executable string, params ...string) (Execution, error)
}

Expand Down Expand Up @@ -127,9 +128,18 @@ func (c *Command) RunShell(shell, script string) error {
//
// Thus the executable needs to be on the PATH of the current process and it is not sufficient to alter the PATH on cmd.Env.
func (c *Command) RunExecutable(executable string, params ...string) error {
return c.RunExecutableWithAttrs(executable, nil, params...)
}

// RunExecutableWithAttrs runs the specified executable with parameters and as a specified UID and GID
// !! While the cmd.Env is applied during command execution, it is NOT involved when the actual executable is resolved.
//
// Thus the executable needs to be on the PATH of the current process and it is not sufficient to alter the PATH on cmd.Env.
func (c *Command) RunExecutableWithAttrs(executable string, sysProcAttr *syscall.SysProcAttr, params ...string) error {
c.prepareOut()

cmd := ExecCommand(executable, params...)
cmd.SysProcAttr = sysProcAttr

if len(c.dir) > 0 {
cmd.Dir = c.dir
Expand Down
4 changes: 4 additions & 0 deletions pkg/mock/fileUtils.go
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,10 @@ func (f *FilesMock) Chmod(path string, mode os.FileMode) error {
return nil
}

func (f *FilesMock) Chown(path string, uid, gid int) error {
return nil
}

func (f *FilesMock) Abs(path string) (string, error) {
f.init()
return f.toAbsPath(path), nil
Expand Down
15 changes: 10 additions & 5 deletions pkg/mock/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"regexp"
"strings"
"syscall"

"github.com/SAP/jenkins-library/pkg/command"
)
Expand All @@ -25,10 +26,11 @@ type ExecMockRunner struct {
}

type ExecCall struct {
Execution *Execution
Async bool
Exec string
Params []string
Execution *Execution
SysProcAttrs *syscall.SysProcAttr
Async bool
Exec string
Params []string
}

type Execution struct {
Expand Down Expand Up @@ -61,8 +63,11 @@ func (m *ExecMockRunner) AppendEnv(e []string) {
}

func (m *ExecMockRunner) RunExecutable(e string, p ...string) error {
return m.RunExecutableWithAttrs(e, nil, p...)
}

exec := ExecCall{Exec: e, Params: p}
func (m *ExecMockRunner) RunExecutableWithAttrs(e string, attrs *syscall.SysProcAttr, p ...string) error {
exec := ExecCall{Exec: e, SysProcAttrs: attrs, Params: p}
m.Calls = append(m.Calls, exec)

c := strings.Join(append([]string{e}, p...), " ")
Expand Down
12 changes: 12 additions & 0 deletions pkg/piperutils/fileUtils.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type FileUtils interface {
FileRemove(path string) error
MkdirAll(path string, perm os.FileMode) error
Chmod(path string, mode os.FileMode) error
Chown(path string, uid, gid int) error
Glob(pattern string) (matches []string, err error)
Chdir(path string) error
TempDir(string, string) (string, error)
Expand Down Expand Up @@ -144,6 +145,17 @@ func (f Files) Chmod(path string, mode os.FileMode) error {
return os.Chmod(path, mode)
}

// Chown is a recursive wrapper for os.Chown().
func (f Files) Chown(path string, uid, gid int) error {
return filepath.WalkDir(path, func(name string, d fs.DirEntry, err error) error {
if err != nil {
return err
}

return os.Chown(name, uid, gid)
})
}

// Unzip will decompress a zip archive, moving all files and folders
// within the zip file (parameter 1) to an output directory (parameter 2).
// from https://golangcode.com/unzip-files-in-go/ with the following license:
Expand Down
3 changes: 3 additions & 0 deletions resources/metadata/cnbBuild.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -363,3 +363,6 @@ spec:
type: sbom
containers:
- image: "paketobuildpacks/builder:base"
options:
- name: -u
value: "0"
rodibrin marked this conversation as resolved.
Show resolved Hide resolved
Loading