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

Set SOURCE_DATE_EPOCH #1418

Merged
merged 8 commits into from
Apr 20, 2022
44 changes: 44 additions & 0 deletions acceptance/acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1974,6 +1974,50 @@ include = [ "*.jar", "media/mountain.jpg", "/media/person.png", ]
})
})
})

when("--creation-time", func() {
it.Before(func() {
h.SkipIf(t, !pack.SupportsFeature(invoke.CreationTime), "")
h.SkipIf(t, !lifecycle.SupportsFeature(config.CreationTime), "")
})

when("provided as 'now'", func() {
it("image has create time of the current time", func() {
expectedTime := time.Now()
pack.RunSuccessfully(
"build", repoName,
"-p", filepath.Join("testdata", "mock_app"),
"--creation-time", "now",
)
assertImage.HasCreateTime(repoName, expectedTime)
})
})

when("provided as unix timestamp", func() {
it("image has create time of the time that was provided", func() {
pack.RunSuccessfully(
"build", repoName,
"-p", filepath.Join("testdata", "mock_app"),
"--creation-time", "1566172801",
)
expectedTime, err := time.Parse("2006-01-02T03:04:05Z", "2019-08-19T00:00:01Z")
h.AssertNil(t, err)
assertImage.HasCreateTime(repoName, expectedTime)
})
})

when("not provided", func() {
it("image has create time of Jan 1, 1980", func() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test could actually run in all compatibility cases, but I'm not sure whether it's worth the time cost of having the tests do that.

pack.RunSuccessfully(
"build", repoName,
"-p", filepath.Join("testdata", "mock_app"),
)
expectedTime, err := time.Parse("2006-01-02T03:04:05Z", "1980-01-01T00:00:01Z")
h.AssertNil(t, err)
assertImage.HasCreateTime(repoName, expectedTime)
})
})
})
})
})

Expand Down
10 changes: 10 additions & 0 deletions acceptance/assertions/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package assertions
import (
"fmt"
"testing"
"time"

"github.com/buildpacks/pack/acceptance/managers"
h "github.com/buildpacks/pack/testhelpers"
Expand Down Expand Up @@ -50,6 +51,15 @@ func (a ImageAssertionManager) HasBaseImage(image, base string) {
}
}

func (a ImageAssertionManager) HasCreateTime(image string, expectedTime time.Time) {
a.testObject.Helper()
inspect, err := a.imageManager.InspectLocal(image)
a.assert.Nil(err)
actualTime, err := time.Parse("2006-01-02T15:04:05Z", inspect.Created)
a.assert.Nil(err)
a.assert.TrueWithMessage(actualTime.Sub(expectedTime) < 5*time.Second && expectedTime.Sub(actualTime) < 5*time.Second, fmt.Sprintf("expected image create time %s to match expected time %s", actualTime, expectedTime))
}

func (a ImageAssertionManager) HasLabelWithData(image, label, data string) {
a.testObject.Helper()
inspect, err := a.imageManager.InspectLocal(image)
Expand Down
18 changes: 17 additions & 1 deletion acceptance/config/lifecycle_asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,23 @@ func (l *LifecycleAsset) JSONOutputForAPIs(baseIndentationWidth int) (

type LifecycleFeature int

var lifecycleFeatureTests = map[LifecycleFeature]func(l *LifecycleAsset) bool{}
const CreationTime = iota

var lifecycleFeatureTests = map[LifecycleFeature]func(l *LifecycleAsset) bool{
CreationTime: func(i *LifecycleAsset) bool {
for _, platformAPI := range i.descriptor.APIs.Platform.Supported {
if platformAPI.AtLeast("0.9") {
return true
}
}
for _, platformAPI := range i.descriptor.APIs.Platform.Deprecated {
if platformAPI.AtLeast("0.9") {
return true
}
}
return false
},
}

func (l *LifecycleAsset) SupportsFeature(f LifecycleFeature) bool {
return lifecycleFeatureTests[f](l)
Expand Down
8 changes: 7 additions & 1 deletion acceptance/invoke/pack.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,13 @@ func (i *PackInvoker) Supports(command string) bool {

type Feature int

var featureTests = map[Feature]func(i *PackInvoker) bool{}
const CreationTime = iota

var featureTests = map[Feature]func(i *PackInvoker) bool{
CreationTime: func(i *PackInvoker) bool {
return i.laterThan("0.24.1")
},
}

func (i *PackInvoker) SupportsFeature(f Feature) bool {
return featureTests[f](i)
Expand Down
4 changes: 2 additions & 2 deletions acceptance/testdata/pack_fixtures/report_output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ Pack:
Version: {{ .Version }}
OS/Arch: {{ .OS }}/{{ .Arch }}

Default Lifecycle Version: 0.13.3
Default Lifecycle Version: 0.14.0

Supported Platform APIs: 0.3, 0.4, 0.5, 0.6, 0.7, 0.8
Supported Platform APIs: 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9

Config:
default-builder-image = "{{ .DefaultBuilder }}"
Expand Down
13 changes: 13 additions & 0 deletions internal/build/lifecycle_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
const (
defaultProcessType = "web"
overrideGID = 0
sourceDateEpochEnv = "SOURCE_DATE_EPOCH"
)

type LifecycleExecution struct {
Expand Down Expand Up @@ -250,6 +251,11 @@ func (l *LifecycleExecution) Create(ctx context.Context, publish bool, dockerHos
cacheOpts = WithBinds(append(volumes, fmt.Sprintf("%s:%s", buildCache.Name(), l.mountPaths.cacheDir()))...)
}

withEnv := NullOp()
if l.opts.CreationTime != nil && l.platformAPI.AtLeast("0.9") {
withEnv = WithEnv(fmt.Sprintf("%s=%s", sourceDateEpochEnv, strconv.Itoa(int(l.opts.CreationTime.Unix()))))
}

opts := []PhaseConfigProviderOperation{
WithFlags(l.withLogLevel(flags...)...),
WithArgs(repoName),
Expand All @@ -263,6 +269,7 @@ func (l *LifecycleExecution) Create(ctx context.Context, publish bool, dockerHos
If(l.opts.Interactive, WithPostContainerRunOperations(
EnsureVolumeAccess(l.opts.Builder.UID(), l.opts.Builder.GID(), l.os, l.layersVolume, l.appVolume),
CopyOut(l.opts.Termui.ReadLayers, l.mountPaths.layersDir(), l.mountPaths.appDir()))),
withEnv,
}

if publish {
Expand Down Expand Up @@ -528,6 +535,11 @@ func (l *LifecycleExecution) newExport(repoName, runImage string, publish bool,
cacheOpt = WithBinds(fmt.Sprintf("%s:%s", buildCache.Name(), l.mountPaths.cacheDir()))
}

withEnv := NullOp()
if l.opts.CreationTime != nil && l.platformAPI.AtLeast("0.9") {
withEnv = WithEnv(fmt.Sprintf("%s=%s", sourceDateEpochEnv, strconv.Itoa(int(l.opts.CreationTime.Unix()))))
}

opts := []PhaseConfigProviderOperation{
WithLogPrefix("exporter"),
WithImage(l.opts.LifecycleImage),
Expand All @@ -550,6 +562,7 @@ func (l *LifecycleExecution) newExport(repoName, runImage string, publish bool,
If(l.opts.Interactive, WithPostContainerRunOperations(
EnsureVolumeAccess(l.opts.Builder.UID(), l.opts.Builder.GID(), l.os, l.layersVolume, l.appVolume),
CopyOut(l.opts.Termui.ReadLayers, l.mountPaths.layersDir(), l.mountPaths.appDir()))),
withEnv,
}

if publish {
Expand Down
149 changes: 149 additions & 0 deletions internal/build/lifecycle_execution_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"math/rand"
"os"
"path/filepath"
"strconv"
"testing"
"time"

Expand Down Expand Up @@ -1053,6 +1054,80 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
h.AssertFunctionName(t, provider.PostContainerRunOps()[1], "CopyOut")
})
})

when("--creation-time", func() {
var fakeBuilder *fakes.FakeBuilder

when("platform < 0.9", func() {
it.Before(func() {
var err error
fakeBuilder, err = fakes.NewFakeBuilder(fakes.WithSupportedPlatformAPIs([]*api.Version{api.MustParse("0.8")}))
h.AssertNil(t, err)
})

it("is ignored", func() {
intTime, err := strconv.ParseInt("1234567890", 10, 64)
h.AssertNil(t, err)
providedTime := time.Unix(intTime, 0).UTC()

lifecycle := newTestLifecycleExec(t, false, func(baseOpts *build.LifecycleOptions) {
baseOpts.CreationTime = &providedTime
}, fakes.WithBuilder(fakeBuilder))
fakePhaseFactory := fakes.NewFakePhaseFactory()

err = lifecycle.Create(context.Background(), false, "", false, "some-run-image", "some-repo-name", "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.AssertSliceNotContains(t, configProvider.ContainerConfig().Env, "SOURCE_DATE_EPOCH=1234567890")
})
})

when("platform >= 0.9", func() {
it.Before(func() {
var err error
fakeBuilder, err = fakes.NewFakeBuilder(fakes.WithSupportedPlatformAPIs([]*api.Version{api.MustParse("0.9")}))
h.AssertNil(t, err)
})

when("provided", func() {
it("configures the phase with env SOURCE_DATE_EPOCH", func() {
intTime, err := strconv.ParseInt("1234567890", 10, 64)
h.AssertNil(t, err)
providedTime := time.Unix(intTime, 0).UTC()

lifecycle := newTestLifecycleExec(t, false, func(baseOpts *build.LifecycleOptions) {
baseOpts.CreationTime = &providedTime
}, fakes.WithBuilder(fakeBuilder))
fakePhaseFactory := fakes.NewFakePhaseFactory()

err = lifecycle.Create(context.Background(), false, "", false, "some-run-image", "some-repo-name", "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.AssertSliceContains(t, configProvider.ContainerConfig().Env, "SOURCE_DATE_EPOCH=1234567890")
})
})

when("not provided", func() {
it("does not panic", func() {
lifecycle := newTestLifecycleExec(t, false, func(baseOpts *build.LifecycleOptions) {
baseOpts.CreationTime = nil
}, fakes.WithBuilder(fakeBuilder))
fakePhaseFactory := fakes.NewFakePhaseFactory()

err := lifecycle.Create(context.Background(), false, "", false, "some-run-image", "some-repo-name", "test", fakeBuildCache, fakeLaunchCache, []string{}, []string{}, fakePhaseFactory)
h.AssertNil(t, err)
})
})
})
})
})

when("#Detect", func() {
Expand Down Expand Up @@ -2618,6 +2693,80 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
h.AssertFunctionName(t, provider.PostContainerRunOps()[1], "CopyOut")
})
})

when("--creation-time", func() {
var fakeBuilder *fakes.FakeBuilder

when("platform < 0.9", func() {
it.Before(func() {
var err error
fakeBuilder, err = fakes.NewFakeBuilder(fakes.WithSupportedPlatformAPIs([]*api.Version{api.MustParse("0.8")}))
h.AssertNil(t, err)
})

it("is ignored", func() {
intTime, err := strconv.ParseInt("1234567890", 10, 64)
h.AssertNil(t, err)
providedTime := time.Unix(intTime, 0).UTC()

lifecycle := newTestLifecycleExec(t, false, func(baseOpts *build.LifecycleOptions) {
baseOpts.CreationTime = &providedTime
}, fakes.WithBuilder(fakeBuilder))
fakePhaseFactory := fakes.NewFakePhaseFactory()

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().Env, "SOURCE_DATE_EPOCH=1234567890")
})
})

when("platform >= 0.9", func() {
it.Before(func() {
var err error
fakeBuilder, err = fakes.NewFakeBuilder(fakes.WithSupportedPlatformAPIs([]*api.Version{api.MustParse("0.9")}))
h.AssertNil(t, err)
})

when("provided", func() {
it("configures the phase with env SOURCE_DATE_EPOCH", func() {
intTime, err := strconv.ParseInt("1234567890", 10, 64)
h.AssertNil(t, err)
providedTime := time.Unix(intTime, 0).UTC()

lifecycle := newTestLifecycleExec(t, false, func(baseOpts *build.LifecycleOptions) {
baseOpts.CreationTime = &providedTime
}, fakes.WithBuilder(fakeBuilder))
fakePhaseFactory := fakes.NewFakePhaseFactory()

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.AssertSliceContains(t, configProvider.ContainerConfig().Env, "SOURCE_DATE_EPOCH=1234567890")
})
})

when("not provided", func() {
it("does not panic", func() {
lifecycle := newTestLifecycleExec(t, false, func(baseOpts *build.LifecycleOptions) {
baseOpts.CreationTime = nil
}, fakes.WithBuilder(fakeBuilder))
fakePhaseFactory := fakes.NewFakePhaseFactory()

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

Expand Down
2 changes: 2 additions & 0 deletions internal/build/lifecycle_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var (
api.MustParse("0.6"),
api.MustParse("0.7"),
api.MustParse("0.8"),
api.MustParse("0.9"),
}
)

Expand Down Expand Up @@ -89,6 +90,7 @@ type LifecycleOptions struct {
GID int
PreviousImage string
SBOMDestinationDir string
CreationTime *time.Time
}

func NewLifecycleExecutor(logger logging.Logger, docker client.CommonAPIClient) *LifecycleExecutor {
Expand Down
2 changes: 1 addition & 1 deletion internal/builder/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (

// A snapshot of the latest tested lifecycle version values
const (
DefaultLifecycleVersion = "0.13.3"
DefaultLifecycleVersion = "0.14.0"
DefaultBuildpackAPIVersion = "0.2"
)

Expand Down
Loading