Skip to content

Commit

Permalink
Merge pull request #1190 from jjbustamante/feature/add-gid-flag-to-su…
Browse files Browse the repository at this point in the history
…pport-openshift

Adding --gid flag to override the user's group id
Signed-off-by: David Freilich <[email protected]>
  • Loading branch information
dfreilich authored May 31, 2021
2 parents 817eb6b + 580439f commit 7a08937
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 3 deletions.
4 changes: 4 additions & 0 deletions build.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ type BuildOptions struct {

// The location at which to mount the AppDir in the build image.
Workspace string

// User's group id used to build the image
GroupID int
}

// ProxyConfig specifies proxy setting to be set as environment variables in a container.
Expand Down Expand Up @@ -305,6 +308,7 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error {
FileFilter: fileFilter,
CacheImage: opts.CacheImage,
Workspace: opts.Workspace,
GID: opts.GroupID,
}

lifecycleVersion := ephemeralBuilder.LifecycleDescriptor().Info.Version
Expand Down
12 changes: 12 additions & 0 deletions build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2191,6 +2191,18 @@ func testBuild(t *testing.T, when spec.G, it spec.S) {
})
})
})

when("gid option", func() {
it("gid is passthroughs to lifecycle", func() {
h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{
Workspace: "app",
Builder: defaultBuilderName,
Image: "example.com/some/repo:tag",
GroupID: 2,
}))
h.AssertEq(t, fakeLifecycle.Opts.GID, 2)
})
})
})
}

Expand Down
16 changes: 16 additions & 0 deletions internal/build/lifecycle_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"math/rand"
"strconv"

"github.com/buildpacks/lifecycle/api"
"github.com/buildpacks/lifecycle/auth"
Expand All @@ -21,6 +22,7 @@ import (

const (
defaultProcessType = "web"
overrideGID = 0
)

type LifecycleExecution struct {
Expand Down Expand Up @@ -182,6 +184,10 @@ func (l *LifecycleExecution) Create(ctx context.Context, publish bool, dockerHos
flags = append(flags, "-skip-restore")
}

if l.opts.GID >= overrideGID {
flags = append(flags, "-gid", strconv.Itoa(l.opts.GID))
}

processType := determineDefaultProcessType(l.platformAPI, l.opts.DefaultProcessType)
if processType != "" {
flags = append(flags, "-process-type", processType)
Expand Down Expand Up @@ -256,6 +262,9 @@ func (l *LifecycleExecution) Restore(ctx context.Context, networkMode string, bu
case cache.Volume:
cacheOpt = WithBinds(fmt.Sprintf("%s:%s", buildCache.Name(), l.mountPaths.cacheDir()))
}
if l.opts.GID >= overrideGID {
flagsOpt = WithFlags("-gid", strconv.Itoa(l.opts.GID))
}

configProvider := NewPhaseConfigProvider(
"restorer",
Expand Down Expand Up @@ -309,6 +318,10 @@ func (l *LifecycleExecution) newAnalyze(repoName, networkMode string, publish bo
cacheOpt = WithBinds(fmt.Sprintf("%s:%s", buildCache.Name(), l.mountPaths.cacheDir()))
}

if l.opts.GID >= overrideGID {
flagsOpt = WithFlags("-gid", strconv.Itoa(l.opts.GID))
}

if publish {
authConfig, err := auth.BuildEnvVar(authn.DefaultKeychain, repoName)
if err != nil {
Expand Down Expand Up @@ -397,6 +410,9 @@ func (l *LifecycleExecution) newExport(repoName, runImage string, publish bool,
if processType != "" {
flags = append(flags, "-process-type", processType)
}
if l.opts.GID >= overrideGID {
flags = append(flags, "-gid", strconv.Itoa(l.opts.GID))
}

cacheOpt := NullOp()
switch buildCache.Type() {
Expand Down
184 changes: 181 additions & 3 deletions internal/build/lifecycle_execution_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,48 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
})
})
})

when("override GID", func() {
when("override GID is provided", func() {
it("configures the phase with the expected arguments", func() {
verboseLifecycle := newTestLifecycleExec(t, true, func(options *build.LifecycleOptions) {
options.GID = 2
})
fakePhaseFactory := fakes.NewFakePhaseFactory()

err := verboseLifecycle.Create(context.Background(), false, "", true, "test", "test", "test", fakeBuildCache, fakeLaunchCache, []string{}, []string{}, fakePhaseFactory)
h.AssertNil(t, err)

lastCallIndex := len(fakePhaseFactory.NewCalledWithProvider) - 1
h.AssertNotEq(t, lastCallIndex, -1)

configProvider := fakePhaseFactory.NewCalledWithProvider[lastCallIndex]
h.AssertEq(t, configProvider.Name(), "creator")
h.AssertIncludeAllExpectedPatterns(t,
configProvider.ContainerConfig().Cmd,
[]string{"-gid", "2"},
)
})
})
when("override GID is not provided", func() {
it("gid is not added to the expected arguments", func() {
verboseLifecycle := newTestLifecycleExec(t, true, func(options *build.LifecycleOptions) {
options.GID = -1
})
fakePhaseFactory := fakes.NewFakePhaseFactory()

err := verboseLifecycle.Create(context.Background(), false, "", true, "test", "test", "test", fakeBuildCache, fakeLaunchCache, []string{}, []string{}, fakePhaseFactory)
h.AssertNil(t, err)

lastCallIndex := len(fakePhaseFactory.NewCalledWithProvider) - 1
h.AssertNotEq(t, lastCallIndex, -1)

configProvider := fakePhaseFactory.NewCalledWithProvider[lastCallIndex]
h.AssertEq(t, configProvider.Name(), "creator")
h.AssertSliceNotContains(t, configProvider.ContainerConfig().Cmd, "-gid")
})
})
})
})

when("#Detect", func() {
Expand Down Expand Up @@ -891,7 +933,9 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
fakeCache.ReturnForType = cache.Image
fakeCache.ReturnForName = "some-cache-image"

lifecycle = newTestLifecycleExec(t, false)
lifecycle = newTestLifecycleExec(t, false, func(options *build.LifecycleOptions) {
options.GID = -1
})
fakePhaseFactory = fakes.NewFakePhaseFactory()
})
it("configures the phase with a build cache images", func() {
Expand Down Expand Up @@ -1036,7 +1080,9 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
})

it("configures the phase with a build cache images", func() {
lifecycle := newTestLifecycleExec(t, false)
lifecycle := newTestLifecycleExec(t, false, func(options *build.LifecycleOptions) {
options.GID = -1
})
fakePhaseFactory := fakes.NewFakePhaseFactory()
expectedRepoName := "some-repo-name"

Expand Down Expand Up @@ -1176,6 +1222,49 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
h.AssertSliceContains(t, configProvider.HostConfig().Binds, expectedBind)
})
})

when("override GID", func() {
var (
lifecycle *build.LifecycleExecution
fakePhaseFactory *fakes.FakePhaseFactory
)
fakePhase := &fakes.FakePhase{}
fakePhaseFactory = fakes.NewFakePhaseFactory(fakes.WhichReturnsForNew(fakePhase))

when("override GID is provided", func() {
it.Before(func() {
lifecycle = newTestLifecycleExec(t, true, func(options *build.LifecycleOptions) {
options.GID = 2
})
})
it("configures the phase with the expected arguments", func() {
err := lifecycle.Analyze(context.Background(), "test", "test", false, "", false, fakeCache, fakePhaseFactory)
h.AssertNil(t, err)
lastCallIndex := len(fakePhaseFactory.NewCalledWithProvider) - 1
h.AssertNotEq(t, lastCallIndex, -1)
configProvider := fakePhaseFactory.NewCalledWithProvider[lastCallIndex]
h.AssertIncludeAllExpectedPatterns(t,
configProvider.ContainerConfig().Cmd,
[]string{"-gid", "2"},
)
})
})
when("override GID is not provided", func() {
it.Before(func() {
lifecycle = newTestLifecycleExec(t, true, func(options *build.LifecycleOptions) {
options.GID = -1
})
})
it("gid is not added to the expected arguments", func() {
err := lifecycle.Analyze(context.Background(), "test", "test", false, "", false, fakeCache, fakePhaseFactory)
h.AssertNil(t, err)
lastCallIndex := len(fakePhaseFactory.NewCalledWithProvider) - 1
h.AssertNotEq(t, lastCallIndex, -1)
configProvider := fakePhaseFactory.NewCalledWithProvider[lastCallIndex]
h.AssertSliceNotContains(t, configProvider.ContainerConfig().Cmd, "-gid")
})
})
})
})

when("#Restore", func() {
Expand Down Expand Up @@ -1292,6 +1381,7 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
configProvider := fakePhaseFactory.NewCalledWithProvider[lastCallIndex]
h.AssertSliceContains(t, configProvider.HostConfig().Binds, expectedBind)
})

when("using cache image", func() {
var (
lifecycle *build.LifecycleExecution
Expand All @@ -1302,7 +1392,9 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
fakeCache.ReturnForType = cache.Image
fakeCache.ReturnForName = "some-cache-image"

lifecycle = newTestLifecycleExec(t, false)
lifecycle = newTestLifecycleExec(t, false, func(options *build.LifecycleOptions) {
options.GID = -1
})
fakePhaseFactory = fakes.NewFakePhaseFactory()
})
it("configures the phase with a cache image", func() {
Expand All @@ -1320,6 +1412,49 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
)
})
})

when("override GID", func() {
var (
lifecycle *build.LifecycleExecution
fakePhaseFactory *fakes.FakePhaseFactory
)
fakePhase := &fakes.FakePhase{}
fakePhaseFactory = fakes.NewFakePhaseFactory(fakes.WhichReturnsForNew(fakePhase))

when("override GID is provided", func() {
it.Before(func() {
lifecycle = newTestLifecycleExec(t, true, func(options *build.LifecycleOptions) {
options.GID = 2
})
})
it("configures the phase with the expected arguments", func() {
err := lifecycle.Restore(context.Background(), "test", fakeCache, fakePhaseFactory)
h.AssertNil(t, err)
lastCallIndex := len(fakePhaseFactory.NewCalledWithProvider) - 1
h.AssertNotEq(t, lastCallIndex, -1)
configProvider := fakePhaseFactory.NewCalledWithProvider[lastCallIndex]
h.AssertIncludeAllExpectedPatterns(t,
configProvider.ContainerConfig().Cmd,
[]string{"-gid", "2"},
)
})
})
when("override GID is not provided", func() {
it.Before(func() {
lifecycle = newTestLifecycleExec(t, true, func(options *build.LifecycleOptions) {
options.GID = -1
})
})
it("gid is not added to the expected arguments", func() {
err := lifecycle.Restore(context.Background(), "test", fakeCache, fakePhaseFactory)
h.AssertNil(t, err)
lastCallIndex := len(fakePhaseFactory.NewCalledWithProvider) - 1
h.AssertNotEq(t, lastCallIndex, -1)
configProvider := fakePhaseFactory.NewCalledWithProvider[lastCallIndex]
h.AssertSliceNotContains(t, configProvider.ContainerConfig().Cmd, "-gid")
})
})
})
})

when("#Build", func() {
Expand Down Expand Up @@ -1870,6 +2005,49 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
})
})
})

when("override GID", func() {
var (
lifecycle *build.LifecycleExecution
fakePhaseFactory *fakes.FakePhaseFactory
)
fakePhase := &fakes.FakePhase{}
fakePhaseFactory = fakes.NewFakePhaseFactory(fakes.WhichReturnsForNew(fakePhase))

when("override GID is provided", func() {
it.Before(func() {
lifecycle = newTestLifecycleExec(t, true, func(options *build.LifecycleOptions) {
options.GID = 2
})
})
it("configures the phase with the expected arguments", func() {
err := lifecycle.Export(context.Background(), "test", "test", false, "", "test", fakeBuildCache, fakeLaunchCache, []string{}, fakePhaseFactory)
h.AssertNil(t, err)
lastCallIndex := len(fakePhaseFactory.NewCalledWithProvider) - 1
h.AssertNotEq(t, lastCallIndex, -1)
configProvider := fakePhaseFactory.NewCalledWithProvider[lastCallIndex]
h.AssertIncludeAllExpectedPatterns(t,
configProvider.ContainerConfig().Cmd,
[]string{"-gid", "2"},
)
})
})
when("override GID is not provided", func() {
it.Before(func() {
lifecycle = newTestLifecycleExec(t, true, func(options *build.LifecycleOptions) {
options.GID = -1
})
})
it("gid is not added to the expected arguments", func() {
err := lifecycle.Export(context.Background(), "test", "test", false, "", "test", fakeBuildCache, fakeLaunchCache, []string{}, fakePhaseFactory)
h.AssertNil(t, err)
lastCallIndex := len(fakePhaseFactory.NewCalledWithProvider) - 1
h.AssertNotEq(t, lastCallIndex, -1)
configProvider := fakePhaseFactory.NewCalledWithProvider[lastCallIndex]
h.AssertSliceNotContains(t, configProvider.ContainerConfig().Cmd, "-gid")
})
})
})
})
}

Expand Down
1 change: 1 addition & 0 deletions internal/build/lifecycle_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type LifecycleOptions struct {
DefaultProcessType string
FileFilter func(string) bool
Workspace string
GID int
}

func NewLifecycleExecutor(logger logging.Logger, docker client.CommonAPIClient) *LifecycleExecutor {
Expand Down
10 changes: 10 additions & 0 deletions internal/commands/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type BuildFlags struct {
Volumes []string
AdditionalTags []string
Workspace string
GID int
}

// Build an image from source code
Expand Down Expand Up @@ -121,6 +122,10 @@ func Build(logger logging.Logger, cfg config.Config, packClient PackClient) *cob
}
lifecycleImage = ref.Name()
}
var gid = -1
if cmd.Flags().Changed("gid") {
gid = flags.GID
}
if err := packClient.Build(cmd.Context(), pack.BuildOptions{
AppPath: flags.AppPath,
Builder: builder,
Expand All @@ -146,6 +151,7 @@ func Build(logger logging.Logger, cfg config.Config, packClient PackClient) *cob
CacheImage: flags.CacheImage,
Workspace: flags.Workspace,
LifecycleImage: lifecycleImage,
GroupID: gid,
}); err != nil {
return errors.Wrap(err, "failed to build")
}
Expand Down Expand Up @@ -184,6 +190,7 @@ This option may set DOCKER_HOST environment variable for the build container if
cmd.Flags().BoolVar(&buildFlags.TrustBuilder, "trust-builder", false, "Trust the provided builder\nAll lifecycle phases will be run in a single container (if supported by the lifecycle).")
cmd.Flags().StringArrayVar(&buildFlags.Volumes, "volume", nil, "Mount host volume into the build container, in the form '<host path>:<target path>[:<options>]'.\n- 'host path': Name of the volume or absolute directory path to mount.\n- 'target path': The path where the file or directory is available in the container.\n- 'options' (default \"ro\"): An optional comma separated list of mount options.\n - \"ro\", volume contents are read-only.\n - \"rw\", volume contents are readable and writeable.\n - \"volume-opt=<key>=<value>\", can be specified more than once, takes a key-value pair consisting of the option name and its value."+multiValueHelp("volume"))
cmd.Flags().StringVar(&buildFlags.Workspace, "workspace", "", "Location at which to mount the app dir in the build image")
cmd.Flags().IntVar(&buildFlags.GID, "gid", 0, `Override GID of user's group in the stack's build and run images. The provided value must be a positive number`)
}

func validateBuildFlags(flags *BuildFlags, cfg config.Config, packClient PackClient, logger logging.Logger) error {
Expand All @@ -195,6 +202,9 @@ func validateBuildFlags(flags *BuildFlags, cfg config.Config, packClient PackCli
return errors.New("cache-image flag requires the publish flag")
}

if flags.GID < 0 {
return errors.New("gid flag must be in the range of 0-2147483647")
}
return nil
}

Expand Down
Loading

0 comments on commit 7a08937

Please sign in to comment.