diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index 6b030cb04c..a6af6bce68 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -834,8 +834,10 @@ func testAcceptance( launchCacheVolume.Clear(context.TODO()) }) - when("builder is untrusted", func() { + when("there are build image extensions", func() { it("uses the 5 phases, and runs the extender (build)", func() { + origLifecycle := lifecycle.Image() + output := pack.RunSuccessfully( "build", repoName, "-p", filepath.Join("testdata", "mock_app"), @@ -846,7 +848,7 @@ func testAcceptance( assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulImageBuild(repoName) assertOutput := assertions.NewLifecycleOutputAssertionManager(t, output) - assertOutput.IncludesLifecycleImageTag(lifecycle.Image()) + assertOutput.IncludesTagOrEphemeralLifecycle(origLifecycle) assertOutput.IncludesSeparatePhasesWithBuildExtension() t.Log("inspecting image") @@ -886,6 +888,8 @@ func testAcceptance( }) it("uses the 5 phases, and runs the extender (run)", func() { + origLifecycle := lifecycle.Image() + output := pack.RunSuccessfully( "build", repoName, "-p", filepath.Join("testdata", "mock_app"), @@ -897,7 +901,8 @@ func testAcceptance( assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulImageBuild(repoName) assertOutput := assertions.NewLifecycleOutputAssertionManager(t, output) - assertOutput.IncludesLifecycleImageTag(lifecycle.Image()) + + assertOutput.IncludesTagOrEphemeralLifecycle(origLifecycle) assertOutput.IncludesSeparatePhasesWithRunExtension() t.Log("inspecting image") @@ -977,6 +982,8 @@ func testAcceptance( when("daemon", func() { it("uses the 5 phases", func() { + origLifecycle := lifecycle.Image() + output := pack.RunSuccessfully( "build", repoName, "-p", filepath.Join("testdata", "mock_app"), @@ -986,13 +993,15 @@ func testAcceptance( assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulImageBuild(repoName) assertOutput := assertions.NewLifecycleOutputAssertionManager(t, output) - assertOutput.IncludesLifecycleImageTag(lifecycle.Image()) + assertOutput.IncludesTagOrEphemeralLifecycle(origLifecycle) assertOutput.IncludesSeparatePhases() }) }) when("--publish", func() { it("uses the 5 phases", func() { + origLifecycle := lifecycle.Image() + buildArgs := []string{ repoName, "-p", filepath.Join("testdata", "mock_app"), @@ -1008,7 +1017,7 @@ func testAcceptance( assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulImageBuild(repoName) assertOutput := assertions.NewLifecycleOutputAssertionManager(t, output) - assertOutput.IncludesLifecycleImageTag(lifecycle.Image()) + assertOutput.IncludesTagOrEphemeralLifecycle(origLifecycle) assertOutput.IncludesSeparatePhases() }) }) diff --git a/acceptance/assertions/lifecycle_output.go b/acceptance/assertions/lifecycle_output.go index 7cc57401c9..fca7b7eec9 100644 --- a/acceptance/assertions/lifecycle_output.go +++ b/acceptance/assertions/lifecycle_output.go @@ -6,6 +6,7 @@ package assertions import ( "fmt" "regexp" + "strings" "testing" h "github.com/buildpacks/pack/testhelpers" @@ -85,8 +86,12 @@ func (l LifecycleOutputAssertionManager) IncludesSeparatePhasesWithRunExtension( l.assert.ContainsAll(l.output, "[detector]", "[analyzer]", "[extender (run)]", "[exporter]") } -func (l LifecycleOutputAssertionManager) IncludesLifecycleImageTag(tag string) { +func (l LifecycleOutputAssertionManager) IncludesTagOrEphemeralLifecycle(tag string) { l.testObject.Helper() - l.assert.Contains(l.output, tag) + if !strings.Contains(l.output, tag) { + if !strings.Contains(l.output, "pack.local/lifecyle") { + l.testObject.Fatalf("Unable to locate reference to lifecycle image within output") + } + } } diff --git a/acceptance/testdata/pack_fixtures/report_output.txt b/acceptance/testdata/pack_fixtures/report_output.txt index 4239830438..6161c9216f 100644 --- a/acceptance/testdata/pack_fixtures/report_output.txt +++ b/acceptance/testdata/pack_fixtures/report_output.txt @@ -4,7 +4,7 @@ Pack: Default Lifecycle Version: 0.19.3 -Supported Platform APIs: 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.10, 0.11, 0.12 +Supported Platform APIs: 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.10, 0.11, 0.12, 0.13 Config: default-builder-image = "{{ .DefaultBuilder }}" diff --git a/go.mod b/go.mod index d074fe0eb6..1d77f5a645 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/buildpacks/pack require ( github.com/BurntSushi/toml v1.3.2 github.com/Masterminds/semver v1.5.0 - github.com/Microsoft/go-winio v0.6.1 + github.com/Microsoft/go-winio v0.6.2 github.com/apex/log v1.9.0 github.com/buildpacks/imgutil v0.0.0-20240206215312-f8d38e1de03d github.com/buildpacks/lifecycle v0.19.3 @@ -12,7 +12,7 @@ require ( github.com/docker/go-connections v0.5.0 github.com/dustin/go-humanize v1.0.1 github.com/gdamore/tcell/v2 v2.7.4 - github.com/go-git/go-git/v5 v5.11.0 + github.com/go-git/go-git/v5 v5.12.0 github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.6.0 github.com/google/go-containerregistry v0.19.1 @@ -20,7 +20,7 @@ require ( github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95 github.com/heroku/color v0.0.6 github.com/mitchellh/ioprogress v0.0.0-20180201004757-6a23b12fa88e - github.com/onsi/gomega v1.32.0 + github.com/onsi/gomega v1.33.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 github.com/pelletier/go-toml v1.9.5 @@ -29,12 +29,12 @@ require ( github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 github.com/sclevine/spec v1.4.0 github.com/spf13/cobra v1.8.0 - golang.org/x/crypto v0.21.0 - golang.org/x/mod v0.16.0 - golang.org/x/oauth2 v0.18.0 - golang.org/x/sync v0.6.0 - golang.org/x/sys v0.18.0 - golang.org/x/term v0.18.0 + golang.org/x/crypto v0.22.0 + golang.org/x/mod v0.17.0 + golang.org/x/oauth2 v0.19.0 + golang.org/x/sync v0.7.0 + golang.org/x/sys v0.19.0 + golang.org/x/term v0.19.0 golang.org/x/text v0.14.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -51,7 +51,7 @@ require ( github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect + github.com/ProtonMail/go-crypto v1.0.0 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/aws/aws-sdk-go-v2 v1.25.2 // indirect github.com/aws/aws-sdk-go-v2/config v1.27.4 // indirect @@ -122,9 +122,9 @@ require ( github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/rivo/uniseg v0.4.3 // indirect - github.com/sergi/go-diff v1.2.0 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/skeema/knownhosts v1.2.1 // indirect + github.com/skeema/knownhosts v1.2.2 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/vbatts/tar-split v0.11.5 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect @@ -132,9 +132,7 @@ require ( go.opentelemetry.io/otel v1.23.0 // indirect go.opentelemetry.io/otel/metric v1.23.0 // indirect go.opentelemetry.io/otel/trace v1.23.0 // indirect - golang.org/x/net v0.22.0 // indirect - golang.org/x/tools v0.17.0 // indirect - google.golang.org/appengine v1.6.8 // indirect + golang.org/x/net v0.23.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/go.sum b/go.sum index 88c9cf662a..7cc9ae8caa 100644 --- a/go.sum +++ b/go.sum @@ -34,12 +34,12 @@ github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= +github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -156,16 +156,16 @@ github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1/go.mod h1:Az6Jt+M5idSED2YPGtwnfJV0kXohgdCBPmHGSYc1r04= github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU= github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg= -github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= +github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= +github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= -github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -192,7 +192,6 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -292,11 +291,11 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= -github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= +github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8= +github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk= -github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg= +github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE= +github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= @@ -345,14 +344,14 @@ github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+e github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8= github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= -github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= +github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs= @@ -372,8 +371,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0= github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= @@ -417,15 +416,15 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -441,11 +440,11 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= +golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -455,8 +454,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -484,8 +483,8 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -494,14 +493,13 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= @@ -523,8 +521,6 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= @@ -548,7 +544,6 @@ gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/internal/build/lifecycle_execution.go b/internal/build/lifecycle_execution.go index 0f5c9a49a4..fa8b8ffd65 100644 --- a/internal/build/lifecycle_execution.go +++ b/internal/build/lifecycle_execution.go @@ -265,7 +265,7 @@ func (l *LifecycleExecution) Run(ctx context.Context, phaseFactoryCreator PhaseF if l.platformAPI.AtLeast("0.10") && l.hasExtensionsForBuild() { group.Go(func() error { l.logger.Info(style.Step("EXTENDING (BUILD)")) - return l.ExtendBuild(ctx, kanikoCache, phaseFactory) + return l.ExtendBuild(ctx, kanikoCache, phaseFactory, l.extensionsAreExperimental()) }) } else { group.Go(func() error { @@ -277,7 +277,7 @@ func (l *LifecycleExecution) Run(ctx context.Context, phaseFactoryCreator PhaseF if l.platformAPI.AtLeast("0.12") && l.hasExtensionsForRun() { group.Go(func() error { l.logger.Info(style.Step("EXTENDING (RUN)")) - return l.ExtendRun(ctx, kanikoCache, phaseFactory, ephemeralRunImage) + return l.ExtendRun(ctx, kanikoCache, phaseFactory, ephemeralRunImage, l.extensionsAreExperimental()) }) } @@ -424,7 +424,7 @@ func (l *LifecycleExecution) Detect(ctx context.Context, phaseFactory PhaseFacto envOp := NullOp() if l.platformAPI.AtLeast("0.10") && l.hasExtensions() { - envOp = WithEnv("CNB_EXPERIMENTAL_MODE=warn") + envOp = If(l.extensionsAreExperimental(), WithEnv("CNB_EXPERIMENTAL_MODE=warn")) } configProvider := NewPhaseConfigProvider( @@ -444,7 +444,7 @@ func (l *LifecycleExecution) Detect(ctx context.Context, phaseFactory PhaseFacto If(l.hasExtensions(), WithPostContainerRunOperations( CopyOutToMaybe(filepath.Join(l.mountPaths.layersDir(), "analyzed.toml"), l.tmpDir))), If(l.hasExtensions(), WithPostContainerRunOperations( - CopyOutToMaybe(filepath.Join(l.mountPaths.layersDir(), "generated", "build"), l.tmpDir))), + CopyOutToMaybe(filepath.Join(l.mountPaths.layersDir(), "generated"), l.tmpDir))), envOp, ) @@ -453,6 +453,10 @@ func (l *LifecycleExecution) Detect(ctx context.Context, phaseFactory PhaseFacto return detect.Run(ctx) } +func (l *LifecycleExecution) extensionsAreExperimental() bool { + return l.PlatformAPI().AtLeast("0.10") && l.platformAPI.LessThan("0.13") +} + func (l *LifecycleExecution) Restore(ctx context.Context, buildCache Cache, kanikoCache Cache, phaseFactory PhaseFactory) error { // build up flags and ops var flags []string @@ -709,7 +713,7 @@ func (l *LifecycleExecution) Build(ctx context.Context, phaseFactory PhaseFactor return build.Run(ctx) } -func (l *LifecycleExecution) ExtendBuild(ctx context.Context, kanikoCache Cache, phaseFactory PhaseFactory) error { +func (l *LifecycleExecution) ExtendBuild(ctx context.Context, kanikoCache Cache, phaseFactory PhaseFactory, experimental bool) error { flags := []string{"-app", l.mountPaths.appDir()} configProvider := NewPhaseConfigProvider( @@ -718,7 +722,7 @@ func (l *LifecycleExecution) ExtendBuild(ctx context.Context, kanikoCache Cache, WithLogPrefix("extender (build)"), WithArgs(l.withLogLevel()...), WithBinds(l.opts.Volumes...), - WithEnv("CNB_EXPERIMENTAL_MODE=warn"), + If(experimental, WithEnv("CNB_EXPERIMENTAL_MODE=warn")), WithFlags(flags...), WithNetwork(l.opts.Network), WithRoot(), @@ -730,7 +734,7 @@ func (l *LifecycleExecution) ExtendBuild(ctx context.Context, kanikoCache Cache, return extend.Run(ctx) } -func (l *LifecycleExecution) ExtendRun(ctx context.Context, kanikoCache Cache, phaseFactory PhaseFactory, runImageName string) error { +func (l *LifecycleExecution) ExtendRun(ctx context.Context, kanikoCache Cache, phaseFactory PhaseFactory, runImageName string, experimental bool) error { flags := []string{"-app", l.mountPaths.appDir(), "-kind", "run"} configProvider := NewPhaseConfigProvider( @@ -739,7 +743,7 @@ func (l *LifecycleExecution) ExtendRun(ctx context.Context, kanikoCache Cache, p WithLogPrefix("extender (run)"), WithArgs(l.withLogLevel()...), WithBinds(l.opts.Volumes...), - WithEnv("CNB_EXPERIMENTAL_MODE=warn"), + If(experimental, WithEnv("CNB_EXPERIMENTAL_MODE=warn")), WithFlags(flags...), WithNetwork(l.opts.Network), WithRoot(), @@ -775,7 +779,7 @@ func (l *LifecycleExecution) Export(ctx context.Context, buildCache, launchCache } else { flags = append(flags, "-run", l.mountPaths.runPath()) if l.hasExtensionsForRun() { - expEnv = WithEnv("CNB_EXPERIMENTAL_MODE=warn") + expEnv = If(l.extensionsAreExperimental(), WithEnv("CNB_EXPERIMENTAL_MODE=warn")) kanikoCacheBindOp = WithBinds(fmt.Sprintf("%s:%s", kanikoCache.Name(), l.mountPaths.kanikoCacheDir())) } } @@ -890,12 +894,25 @@ func (l *LifecycleExecution) hasExtensionsForBuild() bool { if !l.hasExtensions() { return false } - // the directory is /generated/build inside the build container, but `CopyOutTo` only copies the directory - fis, err := os.ReadDir(filepath.Join(l.tmpDir, "build")) + generatedDir := filepath.Join(l.tmpDir, "generated") + fis, err := os.ReadDir(filepath.Join(generatedDir, "build")) + if err == nil && len(fis) > 0 { + // on older platforms, we need to find a file such as /generated/build//Dockerfile + // on newer platforms, /generated/build doesn't exist + return true + } + // on newer platforms, we need to find a file such as /generated//build.Dockerfile + fis, err = os.ReadDir(generatedDir) if err != nil { + l.logger.Warnf("failed to read generated directory, assuming no build image extensions: %s", err) return false } - return len(fis) > 0 + for _, fi := range fis { + if _, err := os.Stat(filepath.Join(generatedDir, fi.Name(), "build.Dockerfile")); err == nil { + return true + } + } + return false } func (l *LifecycleExecution) hasExtensionsForRun() bool { diff --git a/internal/build/lifecycle_execution_test.go b/internal/build/lifecycle_execution_test.go index ed12a2e5b1..24545c4ac2 100644 --- a/internal/build/lifecycle_execution_test.go +++ b/internal/build/lifecycle_execution_test.go @@ -136,11 +136,15 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) { // construct fixtures for extensions if extensionsForBuild { - // the directory is /generated/build inside the build container, but `CopyOutTo` only copies the directory - err = os.MkdirAll(filepath.Join(tmpDir, "build"), 0755) - h.AssertNil(t, err) - _, err = os.Create(filepath.Join(tmpDir, "build", "some-dockerfile")) - h.AssertNil(t, err) + if platformAPI.LessThan("0.13") { + err = os.MkdirAll(filepath.Join(tmpDir, "generated", "build", "some-buildpack-id"), 0755) + h.AssertNil(t, err) + } else { + err = os.MkdirAll(filepath.Join(tmpDir, "generated", "some-buildpack-id"), 0755) + h.AssertNil(t, err) + _, err = os.Create(filepath.Join(tmpDir, "generated", "some-buildpack-id", "build.Dockerfile")) + h.AssertNil(t, err) + } } amd := files.Analyzed{RunImage: &files.RunImage{ Extend: false, @@ -579,7 +583,32 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) { providedOrderExt = dist.Order{dist.OrderEntry{Group: []dist.ModuleRef{ /* don't care */ }}} when("for build", func() { - when("present /generated/build", func() { + when("present in /generated/", func() { + extensionsForBuild = true + + when("platform >= 0.13", func() { + platformAPI = api.MustParse("0.13") + + it("runs the extender (build)", func() { + err := lifecycle.Run(context.Background(), func(execution *build.LifecycleExecution) build.PhaseFactory { + return fakePhaseFactory + }) + h.AssertNil(t, err) + + h.AssertEq(t, len(fakePhaseFactory.NewCalledWithProvider), 5) + + var found bool + for _, entry := range fakePhaseFactory.NewCalledWithProvider { + if entry.Name() == "extender" { + found = true + } + } + h.AssertEq(t, found, true) + }) + }) + }) + + when("present in /generated/build", func() { extensionsForBuild = true when("platform < 0.10", func() { @@ -603,7 +632,7 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) { }) }) - when("platform >= 0.10", func() { + when("platform 0.10 to 0.12", func() { platformAPI = api.MustParse("0.10") it("runs the extender (build)", func() { @@ -2024,8 +2053,10 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) { }) when("#ExtendBuild", func() { + var experimental bool it.Before(func() { - err := lifecycle.ExtendBuild(context.Background(), fakeKanikoCache, fakePhaseFactory) + experimental = true + err := lifecycle.ExtendBuild(context.Background(), fakeKanikoCache, fakePhaseFactory, experimental) h.AssertNil(t, err) lastCallIndex := len(fakePhaseFactory.NewCalledWithProvider) - 1 @@ -2063,11 +2094,31 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) { it("configures the phase with root", func() { h.AssertEq(t, configProvider.ContainerConfig().User, "root") }) + + when("experimental is false", func() { + it.Before(func() { + experimental = false + err := lifecycle.ExtendBuild(context.Background(), fakeKanikoCache, fakePhaseFactory, experimental) + h.AssertNil(t, err) + + lastCallIndex := len(fakePhaseFactory.NewCalledWithProvider) - 1 + h.AssertNotEq(t, lastCallIndex, -1) + + configProvider = fakePhaseFactory.NewCalledWithProvider[lastCallIndex] + h.AssertEq(t, configProvider.Name(), "extender") + }) + + it("CNB_EXPERIMENTAL_MODE=warn is not enable in the environment", func() { + h.AssertSliceNotContains(t, configProvider.ContainerConfig().Env, "CNB_EXPERIMENTAL_MODE=warn") + }) + }) }) when("#ExtendRun", func() { + var experimental bool it.Before(func() { - err := lifecycle.ExtendRun(context.Background(), fakeKanikoCache, fakePhaseFactory, "some-run-image") + experimental = true + err := lifecycle.ExtendRun(context.Background(), fakeKanikoCache, fakePhaseFactory, "some-run-image", experimental) h.AssertNil(t, err) lastCallIndex := len(fakePhaseFactory.NewCalledWithProvider) - 1 @@ -2111,6 +2162,24 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) { it("configures the phase with root", func() { h.AssertEq(t, configProvider.ContainerConfig().User, "root") }) + + when("experimental is false", func() { + it.Before(func() { + experimental = false + err := lifecycle.ExtendRun(context.Background(), fakeKanikoCache, fakePhaseFactory, "some-run-image", experimental) + h.AssertNil(t, err) + + lastCallIndex := len(fakePhaseFactory.NewCalledWithProvider) - 1 + h.AssertNotEq(t, lastCallIndex, -1) + + configProvider = fakePhaseFactory.NewCalledWithProvider[lastCallIndex] + h.AssertEq(t, configProvider.Name(), "extender") + }) + + it("CNB_EXPERIMENTAL_MODE=warn is not enable in the environment", func() { + h.AssertSliceNotContains(t, configProvider.ContainerConfig().Env, "CNB_EXPERIMENTAL_MODE=warn") + }) + }) }) when("#Export", func() { diff --git a/internal/build/lifecycle_executor.go b/internal/build/lifecycle_executor.go index 09d9011f0c..6394f89472 100644 --- a/internal/build/lifecycle_executor.go +++ b/internal/build/lifecycle_executor.go @@ -32,6 +32,7 @@ var ( api.MustParse("0.10"), api.MustParse("0.11"), api.MustParse("0.12"), + api.MustParse("0.13"), } ) @@ -71,7 +72,7 @@ type LifecycleOptions struct { Builder Builder BuilderImage string // differs from Builder.Name() and Builder.Image().Name() in that it includes the registry context LifecycleImage string - LifecycleApis []string // optional - populated only if custom lifecycle image is downloaded, from that lifecycle's container's Labels. + LifecycleApis []string // optional - populated only if custom lifecycle image is downloaded, from that lifecycle image's labels. RunImage string FetchRunImageWithLifecycleLayer func(name string) (string, error) ProjectMetadata files.ProjectMetadata diff --git a/internal/builder/image_fetcher_wrapper.go b/internal/builder/image_fetcher_wrapper.go index dfb0de4a83..16bf39b2a7 100644 --- a/internal/builder/image_fetcher_wrapper.go +++ b/internal/builder/image_fetcher_wrapper.go @@ -13,6 +13,14 @@ type ImageFetcher interface { // If daemon is true, it will look return a `local.Image`. Pull, applicable only when daemon is true, will // attempt to pull a remote image first. Fetch(ctx context.Context, name string, options image.FetchOptions) (imgutil.Image, error) + + // CheckReadAccess verifies if an image is accessible with read permissions + // When FetchOptions.Daemon is true and the image doesn't exist in the daemon, + // the behavior is dictated by the pull policy, which can have the following behavior + // - PullNever: returns false + // - PullAlways Or PullIfNotPresent: it will check read access for the remote image. + // When FetchOptions.Daemon is false it will check read access for the remote image. + CheckReadAccess(repo string, options image.FetchOptions) bool } type ImageFetcherWrapper struct { @@ -32,3 +40,7 @@ func (w *ImageFetcherWrapper) Fetch( ) (Inspectable, error) { return w.fetcher.Fetch(ctx, name, options) } + +func (w *ImageFetcherWrapper) CheckReadAccessValidator(repo string, options image.FetchOptions) bool { + return w.fetcher.CheckReadAccess(repo, options) +} diff --git a/internal/commands/buildpack_new_test.go b/internal/commands/buildpack_new_test.go index 4a988672df..05a76f42b4 100644 --- a/internal/commands/buildpack_new_test.go +++ b/internal/commands/buildpack_new_test.go @@ -97,14 +97,14 @@ func testBuildpackNewCommand(t *testing.T, when spec.G, it spec.S) { Arch: "arm", ArchVariant: "v6", Distributions: []dist.Distribution{{ - Name: "ubuntu", - Versions: []string{"14.04", "16.04"}, + Name: "ubuntu", + Version: "14.04", }}, }}, }).Return(nil).MaxTimes(1) path := filepath.Join(tmpDir, "targets") - command.SetArgs([]string{"--path", path, "example/targets", "--targets", "linux/arm/v6:ubuntu@14.04@16.04"}) + command.SetArgs([]string{"--path", path, "example/targets", "--targets", "linux/arm/v6:ubuntu@14.04"}) err := command.Execute() h.AssertNil(t, err) @@ -120,8 +120,11 @@ func testBuildpackNewCommand(t *testing.T, when spec.G, it spec.S) { Arch: "arm", ArchVariant: "v6", Distributions: []dist.Distribution{{ - Name: "ubuntu", - Versions: []string{"14.04", "16.04"}, + Name: "ubuntu", + Version: "14.04", + }, { + Name: "ubuntu", + Version: "16.04", }}, }}, }).Return(nil).MaxTimes(1) @@ -133,7 +136,7 @@ func testBuildpackNewCommand(t *testing.T, when spec.G, it spec.S) { h.AssertNotNil(t, err) }) when("it should", func() { - it("support format [os][/arch][/variant]:[name@version@version2];[some-name@version@version2]", func() { + it("support format [os][/arch][/variant]:[name@version];[some-name@version]", func() { mockClient.EXPECT().NewBuildpack(gomock.Any(), client.NewBuildpackOptions{ API: "0.8", ID: "example/targets", @@ -146,12 +149,12 @@ func testBuildpackNewCommand(t *testing.T, when spec.G, it spec.S) { ArchVariant: "v6", Distributions: []dist.Distribution{ { - Name: "ubuntu", - Versions: []string{"14.04", "16.04"}, + Name: "ubuntu", + Version: "14.04", }, { - Name: "debian", - Versions: []string{"8.10", "10.9"}, + Name: "debian", + Version: "8.10", }, }, }, @@ -160,8 +163,8 @@ func testBuildpackNewCommand(t *testing.T, when spec.G, it spec.S) { Arch: "amd64", Distributions: []dist.Distribution{ { - Name: "windows-nano", - Versions: []string{"10.0.19041.1415"}, + Name: "windows-nano", + Version: "10.0.19041.1415", }, }, }, @@ -169,7 +172,7 @@ func testBuildpackNewCommand(t *testing.T, when spec.G, it spec.S) { }).Return(nil).MaxTimes(1) path := filepath.Join(tmpDir, "targets") - command.SetArgs([]string{"--path", path, "example/targets", "--targets", "linux/arm/v6:ubuntu@14.04@16.04;debian@8.10@10.9", "-t", "windows/amd64:windows-nano@10.0.19041.1415"}) + command.SetArgs([]string{"--path", path, "example/targets", "--targets", "linux/arm/v6:ubuntu@14.04;debian@8.10", "-t", "windows/amd64:windows-nano@10.0.19041.1415"}) err := command.Execute() h.AssertNil(t, err) diff --git a/internal/fakes/fake_access_checker.go b/internal/fakes/fake_access_checker.go deleted file mode 100644 index 9912df97dc..0000000000 --- a/internal/fakes/fake_access_checker.go +++ /dev/null @@ -1,19 +0,0 @@ -package fakes - -type FakeAccessChecker struct { - RegistriesToFail []string -} - -func NewFakeAccessChecker() *FakeAccessChecker { - return &FakeAccessChecker{} -} - -func (f *FakeAccessChecker) Check(repo string) bool { - for _, toFail := range f.RegistriesToFail { - if toFail == repo { - return false - } - } - - return true -} diff --git a/internal/fakes/fake_image_fetcher.go b/internal/fakes/fake_image_fetcher.go index 3ae30f9610..9e09ecfde2 100644 --- a/internal/fakes/fake_image_fetcher.go +++ b/internal/fakes/fake_image_fetcher.go @@ -55,6 +55,10 @@ func (f *FakeImageFetcher) Fetch(ctx context.Context, name string, options image return ri, nil } +func (f *FakeImageFetcher) CheckReadAccess(_ string, _ image.FetchOptions) bool { + return true +} + func shouldPull(localFound, remoteFound bool, policy image.PullPolicy) bool { if remoteFound && !localFound && policy == image.PullIfNotPresent { return true diff --git a/internal/layer/writer_factory_test.go b/internal/layer/writer_factory_test.go index af9c5cfc59..d39dff77a2 100644 --- a/internal/layer/writer_factory_test.go +++ b/internal/layer/writer_factory_test.go @@ -1,7 +1,7 @@ package layer_test import ( - "archive/tar" + "archive/tar" //nolint "testing" ilayer "github.com/buildpacks/imgutil/layer" diff --git a/internal/target/parse.go b/internal/target/parse.go index f5ea955892..61a6f4da05 100644 --- a/internal/target/parse.go +++ b/internal/target/parse.go @@ -1,6 +1,7 @@ package target import ( + "fmt" "strings" "github.com/pkg/errors" @@ -66,11 +67,15 @@ func ParseDistro(distroString string, logger logging.Logger) (distro dist.Distri if d[0] == "" || len(d) == 0 { return distro, errors.Errorf("distro's versions %s cannot be specified without distro's name", style.Symbol("@"+strings.Join(d[1:], "@"))) } - if len(d) <= 2 && (strings.Contains(strings.Join(d[1:], ""), "") || d[1] == "") { + distro.Name = d[0] + if len(d) < 2 { logger.Warnf("distro with name %s has no specific version!", style.Symbol(d[0])) + return distro, err } - distro.Name = d[0] - distro.Versions = d[1:] + if len(d) > 2 { + return distro, fmt.Errorf("invalid distro: %s", distroString) + } + distro.Version = d[1] return distro, err } diff --git a/internal/target/parse_test.go b/internal/target/parse_test.go index 030f2c772d..61b2c6e39e 100644 --- a/internal/target/parse_test.go +++ b/internal/target/parse_test.go @@ -61,13 +61,14 @@ func testParseTargets(t *testing.T, when spec.G, it spec.S) { h.AssertNotEq(t, outBuf.String(), "") }) }) + when("target#ParseTargets", func() { it("should throw an error when atleast one target throws error", func() { _, err := target.ParseTargets([]string{"linux/arm/v6", ":distro@version"}, logging.NewLogWithWriters(&outBuf, &outBuf)) h.AssertNotNil(t, err) }) it("should parse targets as expected", func() { - output, err := target.ParseTargets([]string{"linux/arm/v6", "linux/amd64:ubuntu@22.04;debian@8.10@10.06"}, logging.NewLogWithWriters(&outBuf, &outBuf)) + output, err := target.ParseTargets([]string{"linux/arm/v6", "linux/amd64:ubuntu@22.04;debian@8.10;debian@10.06"}, logging.NewLogWithWriters(&outBuf, &outBuf)) h.AssertNil(t, err) h.AssertEq(t, output, []dist.Target{ { @@ -80,47 +81,66 @@ func testParseTargets(t *testing.T, when spec.G, it spec.S) { Arch: "amd64", Distributions: []dist.Distribution{ { - Name: "ubuntu", - Versions: []string{"22.04"}, + Name: "ubuntu", + Version: "22.04", + }, + { + Name: "debian", + Version: "8.10", }, { - Name: "debian", - Versions: []string{"8.10", "10.06"}, + Name: "debian", + Version: "10.06", }, }, }, }) }) }) + when("target#ParseDistro", func() { it("should parse distro as expected", func() { - output, err := target.ParseDistro("ubuntu@22.04@20.08", logging.NewLogWithWriters(&outBuf, &outBuf)) + output, err := target.ParseDistro("ubuntu@22.04", logging.NewLogWithWriters(&outBuf, &outBuf)) h.AssertEq(t, output, dist.Distribution{ - Name: "ubuntu", - Versions: []string{"22.04", "20.08"}, + Name: "ubuntu", + Version: "22.04", }) h.AssertNil(t, err) }) - it("should return an error", func() { + it("should return an error when name is missing", func() { _, err := target.ParseDistro("@22.04@20.08", logging.NewLogWithWriters(&outBuf, &outBuf)) h.AssertNotNil(t, err) }) + it("should return an error when there are two versions", func() { + _, err := target.ParseDistro("some-distro@22.04@20.08", logging.NewLogWithWriters(&outBuf, &outBuf)) + h.AssertNotNil(t, err) + h.AssertError(t, err, "invalid distro") + }) it("should warn when distro version is not specified", func() { target.ParseDistro("ubuntu", logging.NewLogWithWriters(&outBuf, &outBuf)) h.AssertNotEq(t, outBuf.String(), "") }) }) + when("target#ParseDistros", func() { it("should parse distros as expected", func() { - output, err := target.ParseDistros("ubuntu@22.04@20.08;debian@8.10@10.06", logging.NewLogWithWriters(&outBuf, &outBuf)) + output, err := target.ParseDistros("ubuntu@22.04;ubuntu@20.08;debian@8.10;debian@10.06", logging.NewLogWithWriters(&outBuf, &outBuf)) h.AssertEq(t, output, []dist.Distribution{ { - Name: "ubuntu", - Versions: []string{"22.04", "20.08"}, + Name: "ubuntu", + Version: "22.04", + }, + { + Name: "ubuntu", + Version: "20.08", + }, + { + Name: "debian", + Version: "8.10", }, { - Name: "debian", - Versions: []string{"8.10", "10.06"}, + Name: "debian", + Version: "10.06", }, }) h.AssertNil(t, err) diff --git a/pkg/buildpack/buildpack.go b/pkg/buildpack/buildpack.go index e658c53e51..c6f9778204 100644 --- a/pkg/buildpack/buildpack.go +++ b/pkg/buildpack/buildpack.go @@ -71,12 +71,16 @@ func FromBlob(descriptor Descriptor, blob Blob) BuildModule { // FromBuildpackRootBlob constructs a buildpack from a blob. It is assumed that the buildpack contents reside at the // root of the blob. The constructed buildpack contents will be structured as per the distribution spec (currently // a tar with contents under '/cnb/buildpacks/{ID}/{version}/*'). -func FromBuildpackRootBlob(blob Blob, layerWriterFactory archive.TarWriterFactory) (BuildModule, error) { +func FromBuildpackRootBlob(blob Blob, layerWriterFactory archive.TarWriterFactory, logger Logger) (BuildModule, error) { descriptor := dist.BuildpackDescriptor{} descriptor.WithAPI = api.MustParse(dist.AssumedBuildpackAPIVersion) - if err := readDescriptor(KindBuildpack, &descriptor, blob); err != nil { + undecodedKeys, err := readDescriptor(KindBuildpack, &descriptor, blob) + if err != nil { return nil, err } + if len(undecodedKeys) > 0 { + logger.Warnf("Ignoring unexpected key(s) in descriptor for buildpack %s: %s", descriptor.EscapedID(), strings.Join(undecodedKeys, ",")) + } if err := detectPlatformSpecificValues(&descriptor, blob); err != nil { return nil, err } @@ -89,22 +93,26 @@ func FromBuildpackRootBlob(blob Blob, layerWriterFactory archive.TarWriterFactor // FromExtensionRootBlob constructs an extension from a blob. It is assumed that the extension contents reside at the // root of the blob. The constructed extension contents will be structured as per the distribution spec (currently // a tar with contents under '/cnb/extensions/{ID}/{version}/*'). -func FromExtensionRootBlob(blob Blob, layerWriterFactory archive.TarWriterFactory) (BuildModule, error) { +func FromExtensionRootBlob(blob Blob, layerWriterFactory archive.TarWriterFactory, logger Logger) (BuildModule, error) { descriptor := dist.ExtensionDescriptor{} descriptor.WithAPI = api.MustParse(dist.AssumedBuildpackAPIVersion) - if err := readDescriptor(KindExtension, &descriptor, blob); err != nil { + undecodedKeys, err := readDescriptor(KindExtension, &descriptor, blob) + if err != nil { return nil, err } + if len(undecodedKeys) > 0 { + logger.Warnf("Ignoring unexpected key(s) in descriptor for extension %s: %s", descriptor.EscapedID(), strings.Join(undecodedKeys, ",")) + } if err := validateExtensionDescriptor(descriptor); err != nil { return nil, err } return buildpackFrom(&descriptor, blob, layerWriterFactory) } -func readDescriptor(kind string, descriptor interface{}, blob Blob) error { +func readDescriptor(kind string, descriptor interface{}, blob Blob) (undecodedKeys []string, err error) { rc, err := blob.Open() if err != nil { - return errors.Wrapf(err, "open %s", kind) + return undecodedKeys, errors.Wrapf(err, "open %s", kind) } defer rc.Close() @@ -112,15 +120,20 @@ func readDescriptor(kind string, descriptor interface{}, blob Blob) error { _, buf, err := archive.ReadTarEntry(rc, descriptorFile) if err != nil { - return errors.Wrapf(err, "reading %s", descriptorFile) + return undecodedKeys, errors.Wrapf(err, "reading %s", descriptorFile) } - _, err = toml.Decode(string(buf), descriptor) + md, err := toml.Decode(string(buf), descriptor) if err != nil { - return errors.Wrapf(err, "decoding %s", descriptorFile) + return undecodedKeys, errors.Wrapf(err, "decoding %s", descriptorFile) } - return nil + undecoded := md.Undecoded() + for _, k := range undecoded { + undecodedKeys = append(undecodedKeys, k.String()) + } + + return undecodedKeys, nil } func detectPlatformSpecificValues(descriptor *dist.BuildpackDescriptor, blob Blob) error { diff --git a/pkg/buildpack/buildpack_test.go b/pkg/buildpack/buildpack_test.go index 4536a5e79e..2de8975630 100644 --- a/pkg/buildpack/buildpack_test.go +++ b/pkg/buildpack/buildpack_test.go @@ -1,6 +1,7 @@ package buildpack_test import ( + "bytes" "fmt" "io" "os" @@ -20,6 +21,7 @@ import ( "github.com/buildpacks/pack/pkg/blob" "github.com/buildpacks/pack/pkg/buildpack" "github.com/buildpacks/pack/pkg/dist" + "github.com/buildpacks/pack/pkg/logging" h "github.com/buildpacks/pack/testhelpers" ) @@ -54,11 +56,10 @@ func testBuildpack(t *testing.T, when spec.G, it spec.S) { when("#BuildpackFromRootBlob", func() { it("parses the descriptor file", func() { - bp, err := buildpack.FromBuildpackRootBlob( - &readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` + bp, err := buildpack.FromBuildpackRootBlob(&readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` api = "0.3" [buildpack] @@ -69,11 +70,9 @@ homepage = "http://geocities.com/cool-bp" [[stacks]] id = "some.stack.id" `)) - return tarBuilder.Reader(archive.DefaultTarWriterFactory()) - }, + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) }, - archive.DefaultTarWriterFactory(), - ) + }, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) h.AssertEq(t, bp.Descriptor().API().String(), "0.3") @@ -84,11 +83,10 @@ id = "some.stack.id" }) it("translates blob to distribution format", func() { - bp, err := buildpack.FromBuildpackRootBlob( - &readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` + bp, err := buildpack.FromBuildpackRootBlob(&readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` api = "0.3" [buildpack] @@ -99,14 +97,12 @@ version = "1.2.3" id = "some.stack.id" `)) - tarBuilder.AddDir("bin", 0700, time.Now()) - tarBuilder.AddFile("bin/detect", 0700, time.Now(), []byte("detect-contents")) - tarBuilder.AddFile("bin/build", 0700, time.Now(), []byte("build-contents")) - return tarBuilder.Reader(archive.DefaultTarWriterFactory()) - }, + tarBuilder.AddDir("bin", 0700, time.Now()) + tarBuilder.AddFile("bin/detect", 0700, time.Now(), []byte("detect-contents")) + tarBuilder.AddFile("bin/build", 0700, time.Now(), []byte("build-contents")) + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) }, - archive.DefaultTarWriterFactory(), - ) + }, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) h.AssertNil(t, bp.Descriptor().EnsureTargetSupport(dist.DefaultTargetOSLinux, dist.DefaultTargetArch, "", "")) @@ -151,11 +147,10 @@ id = "some.stack.id" }) it("translates blob to windows bat distribution format", func() { - bp, err := buildpack.FromBuildpackRootBlob( - &readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` + bp, err := buildpack.FromBuildpackRootBlob(&readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` api = "0.9" [buildpack] @@ -163,14 +158,12 @@ id = "bp.one" version = "1.2.3" `)) - tarBuilder.AddDir("bin", 0700, time.Now()) - tarBuilder.AddFile("bin/detect", 0700, time.Now(), []byte("detect-contents")) - tarBuilder.AddFile("bin/build.bat", 0700, time.Now(), []byte("build-contents")) - return tarBuilder.Reader(archive.DefaultTarWriterFactory()) - }, + tarBuilder.AddDir("bin", 0700, time.Now()) + tarBuilder.AddFile("bin/detect", 0700, time.Now(), []byte("detect-contents")) + tarBuilder.AddFile("bin/build.bat", 0700, time.Now(), []byte("build-contents")) + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) }, - archive.DefaultTarWriterFactory(), - ) + }, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) bpDescriptor := bp.Descriptor().(*dist.BuildpackDescriptor) @@ -189,11 +182,10 @@ version = "1.2.3" }) it("translates blob to windows exe distribution format", func() { - bp, err := buildpack.FromBuildpackRootBlob( - &readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` + bp, err := buildpack.FromBuildpackRootBlob(&readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` api = "0.3" [buildpack] @@ -201,14 +193,12 @@ id = "bp.one" version = "1.2.3" `)) - tarBuilder.AddDir("bin", 0700, time.Now()) - tarBuilder.AddFile("bin/detect", 0700, time.Now(), []byte("detect-contents")) - tarBuilder.AddFile("bin/build.exe", 0700, time.Now(), []byte("build-contents")) - return tarBuilder.Reader(archive.DefaultTarWriterFactory()) - }, + tarBuilder.AddDir("bin", 0700, time.Now()) + tarBuilder.AddFile("bin/detect", 0700, time.Now(), []byte("detect-contents")) + tarBuilder.AddFile("bin/build.exe", 0700, time.Now(), []byte("build-contents")) + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) }, - archive.DefaultTarWriterFactory(), - ) + }, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) bpDescriptor := bp.Descriptor().(*dist.BuildpackDescriptor) @@ -244,13 +234,10 @@ id = "some.stack.id" }, } - bp, err := buildpack.FromBuildpackRootBlob( - &errorBlob{ - realBlob: realBlob, - limit: 4, - }, - archive.DefaultTarWriterFactory(), - ) + bp, err := buildpack.FromBuildpackRootBlob(&errorBlob{ + realBlob: realBlob, + limit: 4, + }, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) bpReader, err := bp.Open() @@ -274,17 +261,14 @@ id = "some.stack.id" when("no exec bits set", func() { it("sets to 0755 if directory", func() { - bp, err := buildpack.FromBuildpackRootBlob( - &readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(bpTOMLData)) - tarBuilder.AddDir("some-dir", 0600, time.Now()) - return tarBuilder.Reader(archive.DefaultTarWriterFactory()) - }, + bp, err := buildpack.FromBuildpackRootBlob(&readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(bpTOMLData)) + tarBuilder.AddDir("some-dir", 0600, time.Now()) + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) }, - archive.DefaultTarWriterFactory(), - ) + }, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) tarPath := writeBlobToFile(bp) @@ -299,18 +283,15 @@ id = "some.stack.id" when("no exec bits set", func() { it("sets to 0755 if 'bin/detect' or 'bin/build'", func() { - bp, err := buildpack.FromBuildpackRootBlob( - &readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(bpTOMLData)) - tarBuilder.AddFile("bin/detect", 0600, time.Now(), []byte("detect-contents")) - tarBuilder.AddFile("bin/build", 0600, time.Now(), []byte("build-contents")) - return tarBuilder.Reader(archive.DefaultTarWriterFactory()) - }, + bp, err := buildpack.FromBuildpackRootBlob(&readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(bpTOMLData)) + tarBuilder.AddFile("bin/detect", 0600, time.Now(), []byte("detect-contents")) + tarBuilder.AddFile("bin/build", 0600, time.Now(), []byte("build-contents")) + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) }, - archive.DefaultTarWriterFactory(), - ) + }, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) bpDescriptor := bp.Descriptor().(*dist.BuildpackDescriptor) @@ -334,17 +315,14 @@ id = "some.stack.id" when("not directory, 'bin/detect', or 'bin/build'", func() { it("sets to 0755 if ANY exec bit is set", func() { - bp, err := buildpack.FromBuildpackRootBlob( - &readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(bpTOMLData)) - tarBuilder.AddFile("some-file", 0700, time.Now(), []byte("some-data")) - return tarBuilder.Reader(archive.DefaultTarWriterFactory()) - }, + bp, err := buildpack.FromBuildpackRootBlob(&readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(bpTOMLData)) + tarBuilder.AddFile("some-file", 0700, time.Now(), []byte("some-data")) + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) }, - archive.DefaultTarWriterFactory(), - ) + }, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) tarPath := writeBlobToFile(bp) @@ -359,17 +337,14 @@ id = "some.stack.id" when("not directory, 'bin/detect', or 'bin/build'", func() { it("sets to 0644 if NO exec bits set", func() { - bp, err := buildpack.FromBuildpackRootBlob( - &readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(bpTOMLData)) - tarBuilder.AddFile("some-file", 0600, time.Now(), []byte("some-data")) - return tarBuilder.Reader(archive.DefaultTarWriterFactory()) - }, + bp, err := buildpack.FromBuildpackRootBlob(&readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(bpTOMLData)) + tarBuilder.AddFile("some-file", 0600, time.Now(), []byte("some-data")) + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) }, - archive.DefaultTarWriterFactory(), - ) + }, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) tarPath := writeBlobToFile(bp) @@ -385,37 +360,31 @@ id = "some.stack.id" when("there is no descriptor file", func() { it("returns error", func() { - _, err := buildpack.FromBuildpackRootBlob( - &readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - return tarBuilder.Reader(archive.DefaultTarWriterFactory()) - }, + _, err := buildpack.FromBuildpackRootBlob(&readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) }, - archive.DefaultTarWriterFactory(), - ) + }, archive.DefaultTarWriterFactory(), nil) h.AssertError(t, err, "could not find entry path 'buildpack.toml'") }) }) when("there is no api field", func() { it("assumes an api version", func() { - bp, err := buildpack.FromBuildpackRootBlob( - &readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` + bp, err := buildpack.FromBuildpackRootBlob(&readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` [buildpack] id = "bp.one" version = "1.2.3" [[stacks]] id = "some.stack.id"`)) - return tarBuilder.Reader(archive.DefaultTarWriterFactory()) - }, + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) }, - archive.DefaultTarWriterFactory(), - ) + }, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) h.AssertEq(t, bp.Descriptor().API().String(), "0.1") }) @@ -423,55 +392,48 @@ id = "some.stack.id"`)) when("there is no id", func() { it("returns error", func() { - _, err := buildpack.FromBuildpackRootBlob( - &readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` + _, err := buildpack.FromBuildpackRootBlob(&readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` [buildpack] id = "" version = "1.2.3" [[stacks]] id = "some.stack.id"`)) - return tarBuilder.Reader(archive.DefaultTarWriterFactory()) - }, + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) }, - archive.DefaultTarWriterFactory(), - ) + }, archive.DefaultTarWriterFactory(), nil) h.AssertError(t, err, "'buildpack.id' is required") }) }) when("there is no version", func() { it("returns error", func() { - _, err := buildpack.FromBuildpackRootBlob( - &readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` + _, err := buildpack.FromBuildpackRootBlob(&readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` [buildpack] id = "bp.one" version = "" [[stacks]] id = "some.stack.id"`)) - return tarBuilder.Reader(archive.DefaultTarWriterFactory()) - }, + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) }, - archive.DefaultTarWriterFactory(), - ) + }, archive.DefaultTarWriterFactory(), nil) h.AssertError(t, err, "'buildpack.version' is required") }) }) when("both stacks and order are present", func() { it("returns error", func() { - _, err := buildpack.FromBuildpackRootBlob( - &readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` + _, err := buildpack.FromBuildpackRootBlob(&readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` [buildpack] id = "bp.one" version = "1.2.3" @@ -484,31 +446,26 @@ id = "some.stack.id" id = "bp.nested" version = "bp.nested.version" `)) - return tarBuilder.Reader(archive.DefaultTarWriterFactory()) - }, + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) }, - archive.DefaultTarWriterFactory(), - ) + }, archive.DefaultTarWriterFactory(), nil) h.AssertError(t, err, "cannot have both 'targets'/'stacks' and an 'order' defined") }) }) when("missing stacks and order", func() { it("does not return an error", func() { - _, err := buildpack.FromBuildpackRootBlob( - &readerBlob{ - openFn: func() io.ReadCloser { - tarBuilder := archive.TarBuilder{} - tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` + _, err := buildpack.FromBuildpackRootBlob(&readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` [buildpack] id = "bp.one" version = "1.2.3" `)) - return tarBuilder.Reader(archive.DefaultTarWriterFactory()) - }, + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) }, - archive.DefaultTarWriterFactory(), - ) + }, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) }) }) @@ -528,10 +485,7 @@ version = "1.2.3" }) it("hardlink is preserved in the output tar file", func() { - bp, err := buildpack.FromBuildpackRootBlob( - blob.NewBlob(bpRootFolder), - archive.DefaultTarWriterFactory(), - ) + bp, err := buildpack.FromBuildpackRootBlob(blob.NewBlob(bpRootFolder), archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) tarPath := writeBlobToFile(bp) @@ -544,6 +498,40 @@ version = "1.2.3" ) }) }) + + when("there are wrong things in the file", func() { + it("warns", func() { + outBuf := bytes.Buffer{} + logger := logging.NewLogWithWriters(&outBuf, &outBuf) + _, err := buildpack.FromBuildpackRootBlob(&readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` +api = "0.3" + +[buildpack] +id = "bp.one" +version = "1.2.3" +homepage = "http://geocities.com/cool-bp" + +[[targets]] +os = "some-os" +arch = "some-arch" +variant = "some-arch-variant" +[[targets.distributions]] +name = "some-distro-name" +version = "some-distro-version" +[[targets.distros]] +name = "some-distro-name" +versions = ["some-distro-version"] +`)) + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) + }, + }, archive.DefaultTarWriterFactory(), logger) + h.AssertNil(t, err) + h.AssertContains(t, outBuf.String(), "Warning: Ignoring unexpected key(s) in descriptor for buildpack bp.one: targets.distributions,targets.distributions.name,targets.distributions.version,targets.distros.versions") + }) + }) }) when("#Match", func() { diff --git a/pkg/buildpack/downloader.go b/pkg/buildpack/downloader.go index 454afda0cb..0a8a0d64cf 100644 --- a/pkg/buildpack/downloader.go +++ b/pkg/buildpack/downloader.go @@ -29,6 +29,7 @@ type Logger interface { type ImageFetcher interface { Fetch(ctx context.Context, name string, options image.FetchOptions) (imgutil.Image, error) + CheckReadAccess(repo string, options image.FetchOptions) bool } type Downloader interface { @@ -141,7 +142,7 @@ func (c *buildpackDownloader) Download(ctx context.Context, moduleURI string, op return nil, nil, errors.Wrapf(err, "downloading %s from %s", kind, style.Symbol(moduleURI)) } - mainBP, depBPs, err = decomposeBlob(blob, kind, opts.ImageOS) + mainBP, depBPs, err = decomposeBlob(blob, kind, opts.ImageOS, c.logger) if err != nil { return nil, nil, errors.Wrapf(err, "extracting from %s", style.Symbol(moduleURI)) } @@ -153,7 +154,7 @@ func (c *buildpackDownloader) Download(ctx context.Context, moduleURI string, op // decomposeBlob decomposes a buildpack or extension blob into the main module (order buildpack or extension) and // (for buildpack blobs) its dependent buildpacks. -func decomposeBlob(blob blob.Blob, kind string, imageOS string) (mainModule BuildModule, depModules []BuildModule, err error) { +func decomposeBlob(blob blob.Blob, kind string, imageOS string, logger Logger) (mainModule BuildModule, depModules []BuildModule, err error) { isOCILayout, err := IsOCILayoutBlob(blob) if err != nil { return mainModule, depModules, errors.Wrapf(err, "inspecting %s blob", kind) @@ -171,9 +172,9 @@ func decomposeBlob(blob blob.Blob, kind string, imageOS string) (mainModule Buil } if kind == KindExtension { - mainModule, err = FromExtensionRootBlob(blob, layerWriterFactory) + mainModule, err = FromExtensionRootBlob(blob, layerWriterFactory, logger) } else { - mainModule, err = FromBuildpackRootBlob(blob, layerWriterFactory) + mainModule, err = FromBuildpackRootBlob(blob, layerWriterFactory, logger) } if err != nil { return mainModule, depModules, errors.Wrapf(err, "reading %s", kind) diff --git a/pkg/client/build.go b/pkg/client/build.go index 64b34cefb4..e5bbcb7eee 100644 --- a/pkg/client/build.go +++ b/pkg/client/build.go @@ -13,6 +13,7 @@ import ( "path/filepath" "runtime" "sort" + "strconv" "strings" "time" @@ -32,6 +33,7 @@ import ( "github.com/buildpacks/pack/internal/build" "github.com/buildpacks/pack/internal/builder" internalConfig "github.com/buildpacks/pack/internal/config" + "github.com/buildpacks/pack/internal/layer" pname "github.com/buildpacks/pack/internal/name" "github.com/buildpacks/pack/internal/paths" "github.com/buildpacks/pack/internal/stack" @@ -340,13 +342,13 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error { return errors.Wrapf(err, "invalid builder %s", style.Symbol(opts.Builder)) } - runImageName := c.resolveRunImage(opts.RunImage, imgRegistry, builderRef.Context().RegistryStr(), bldr.DefaultRunImage(), opts.AdditionalMirrors, opts.Publish, c.accessChecker) - fetchOptions := image.FetchOptions{ Daemon: !opts.Publish, PullPolicy: opts.PullPolicy, Platform: fmt.Sprintf("%s/%s", builderOS, builderArch), } + runImageName := c.resolveRunImage(opts.RunImage, imgRegistry, builderRef.Context().RegistryStr(), bldr.DefaultRunImage(), opts.AdditionalMirrors, opts.Publish, fetchOptions) + if opts.Layout() { targetRunImagePath, err := layout.ParseRefToPath(runImageName) if err != nil { @@ -425,6 +427,29 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error { return fmt.Errorf("fetching lifecycle image: %w", err) } + // if lifecyle container os isn't windows, use ephemeral lifecycle to add /workspace with correct ownership + imageOS, err := lifecycleImage.OS() + if err != nil { + return errors.Wrap(err, "getting lifecycle image OS") + } + if imageOS != "windows" { + // obtain uid/gid from builder to use when extending lifecycle image + uid, gid, err := userAndGroupIDs(rawBuilderImage) + if err != nil { + return fmt.Errorf("obtaining build uid/gid from builder image: %w", err) + } + + c.logger.Debugf("Creating ephemeral lifecycle from %s with uid %d and gid %d. With workspace dir %s", lifecycleImage.Name(), uid, gid, opts.Workspace) + // extend lifecycle image with mountpoints, and use it instead of current lifecycle image + lifecycleImage, err = c.createEphemeralLifecycle(lifecycleImage, opts.Workspace, uid, gid) + if err != nil { + return err + } + c.logger.Debugf("Selecting ephemeral lifecycle image %s for build", lifecycleImage.Name()) + // cleanup the extended lifecycle image when done + defer c.docker.ImageRemove(context.Background(), lifecycleImage.Name(), types.ImageRemoveOptions{Force: true}) + } + lifecycleOptsLifecycleImage = lifecycleImage.Name() labels, err := lifecycleImage.Labels() if err != nil { @@ -467,9 +492,6 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error { defer c.docker.ImageRemove(context.Background(), ephemeralBuilder.Name(), types.ImageRemoveOptions{Force: true}) if len(bldr.OrderExtensions()) > 0 || len(ephemeralBuilder.OrderExtensions()) > 0 { - if !c.experimental { - return fmt.Errorf("experimental features must be enabled when builder contains image extensions") - } if builderOS == "windows" { return fmt.Errorf("builder contains image extensions which are not supported for Windows builds") } @@ -599,7 +621,60 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error { return "", err } - lifecycleLayerName, err := LifecycleLayerName(lifecycleImageTar) + advanceTarToEntryWithName := func(tarReader *tar.Reader, wantName string) (*tar.Header, error) { + var ( + header *tar.Header + err error + ) + for { + header, err = tarReader.Next() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + if header.Name != wantName { + continue + } + return header, nil + } + return nil, fmt.Errorf("failed to find header with name: %s", wantName) + } + lifecycleLayerName, err := func() (string, error) { + lifecycleImageReader, err := os.Open(lifecycleImageTar) + if err != nil { + return "", err + } + defer lifecycleImageReader.Close() + tarReader := tar.NewReader(lifecycleImageReader) + if _, err = advanceTarToEntryWithName(tarReader, "manifest.json"); err != nil { + return "", err + } + type descriptor struct { + Layers []string + } + type manifestJSON []descriptor + var manifestContents manifestJSON + if err = json.NewDecoder(tarReader).Decode(&manifestContents); err != nil { + return "", err + } + if len(manifestContents) < 1 { + return "", errors.New("missing manifest entries") + } + // we can assume the lifecycle layer is the last in the tar, except if the lifecycle has been extended as an ephemeral lifecycle + layerOffset := 1 + if strings.Contains(lifecycleOpts.LifecycleImage, "pack.local/lifecycle") { + layerOffset = 2 + } + + if (len(manifestContents[0].Layers) - layerOffset) < 0 { + return "", errors.New("Lifecycle image did not contain expected layer count") + } + + return manifestContents[0].Layers[len(manifestContents[0].Layers)-layerOffset], nil + }() + if err != nil { return "", err } @@ -1258,6 +1333,109 @@ func (c *Client) processExtensions(ctx context.Context, builderImage imgutil.Ima return fetchedExs, orderExtensions, nil } +func userAndGroupIDs(img imgutil.Image) (int, int, error) { + sUID, err := img.Env(builder.EnvUID) + if err != nil { + return 0, 0, errors.Wrap(err, "reading builder env variables") + } else if sUID == "" { + return 0, 0, fmt.Errorf("image %s missing required env var %s", style.Symbol(img.Name()), style.Symbol(builder.EnvUID)) + } + + sGID, err := img.Env(builder.EnvGID) + if err != nil { + return 0, 0, errors.Wrap(err, "reading builder env variables") + } else if sGID == "" { + return 0, 0, fmt.Errorf("image %s missing required env var %s", style.Symbol(img.Name()), style.Symbol(builder.EnvGID)) + } + + var uid, gid int + uid, err = strconv.Atoi(sUID) + if err != nil { + return 0, 0, fmt.Errorf("failed to parse %s, value %s should be an integer", style.Symbol(builder.EnvUID), style.Symbol(sUID)) + } + + gid, err = strconv.Atoi(sGID) + if err != nil { + return 0, 0, fmt.Errorf("failed to parse %s, value %s should be an integer", style.Symbol(builder.EnvGID), style.Symbol(sGID)) + } + + return uid, gid, nil +} + +func workspacePathForOS(os, workspace string) string { + if workspace == "" { + workspace = "workspace" + } + if os == "windows" { + // note we don't use ephemeral lifecycle when os is windows.. + return "c:\\" + workspace + } + return "/" + workspace +} + +func (c *Client) addUserMountpoints(lifecycleImage imgutil.Image, dest string, workspace string, uid int, gid int) (string, error) { + // today only workspace needs to be added, easy to add future dirs if required. + + imageOS, err := lifecycleImage.OS() + if err != nil { + return "", errors.Wrap(err, "getting image OS") + } + layerWriterFactory, err := layer.NewWriterFactory(imageOS) + if err != nil { + return "", err + } + + workspace = workspacePathForOS(imageOS, workspace) + + fh, err := os.Create(filepath.Join(dest, "dirs.tar")) + if err != nil { + return "", err + } + defer fh.Close() + + lw := layerWriterFactory.NewWriter(fh) + defer lw.Close() + + for _, path := range []string{workspace} { + if err := lw.WriteHeader(&tar.Header{ + Typeflag: tar.TypeDir, + Name: path, + Mode: 0755, + ModTime: archive.NormalizedDateTime, + Uid: uid, + Gid: gid, + }); err != nil { + return "", errors.Wrapf(err, "creating %s mountpoint dir in layer", style.Symbol(path)) + } + } + + return fh.Name(), nil +} + +func (c *Client) createEphemeralLifecycle(lifecycleImage imgutil.Image, workspace string, uid int, gid int) (imgutil.Image, error) { + lifecycleImage.Rename(fmt.Sprintf("pack.local/lifecycle/%x:latest", randString(10))) + + tmpDir, err := os.MkdirTemp("", "create-lifecycle-scratch") + if err != nil { + return nil, err + } + defer os.RemoveAll(tmpDir) + dirsTar, err := c.addUserMountpoints(lifecycleImage, tmpDir, workspace, uid, gid) + if err != nil { + return nil, err + } + if err := lifecycleImage.AddLayer(dirsTar); err != nil { + return nil, errors.Wrap(err, "adding mountpoint dirs layer") + } + + err = lifecycleImage.Save() + if err != nil { + return nil, err + } + + return lifecycleImage, nil +} + func (c *Client) createEphemeralBuilder( rawBuilderImage imgutil.Image, env map[string]string, diff --git a/pkg/client/build_test.go b/pkg/client/build_test.go index 27cd02d236..3bf7eb1075 100644 --- a/pkg/client/build_test.go +++ b/pkg/client/build_test.go @@ -56,7 +56,6 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { subject *Client fakeImageFetcher *ifakes.FakeImageFetcher fakeLifecycle *ifakes.FakeLifecycle - fakeAccessChecker *ifakes.FakeAccessChecker defaultBuilderStackID = "some.stack.id" defaultWindowsBuilderStackID = "some.windows.stack.id" defaultBuilderImage *fakes.Image @@ -81,7 +80,6 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { var err error fakeImageFetcher = ifakes.NewFakeImageFetcher() - fakeAccessChecker = ifakes.NewFakeAccessChecker() fakeLifecycle = &ifakes.FakeLifecycle{} tmpDir, err = os.MkdirTemp("", "build-test") @@ -138,7 +136,6 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { logger: logger, imageFetcher: fakeImageFetcher, downloader: blobDownloader, - accessChecker: fakeAccessChecker, lifecycleExecutor: fakeLifecycle, docker: docker, buildpackDownloader: buildpackDownloader, @@ -2101,6 +2098,8 @@ api = "0.2" when("builder is untrusted", func() { when("lifecycle image is available", func() { it("uses the 5 phases with the lifecycle image", func() { + origLifecyleName := fakeLifecycleImage.Name() + h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ Image: "some/app", Builder: defaultBuilderName, @@ -2108,9 +2107,9 @@ api = "0.2" TrustBuilder: func(string) bool { return false }, })) h.AssertEq(t, fakeLifecycle.Opts.UseCreator, false) - h.AssertEq(t, fakeLifecycle.Opts.LifecycleImage, fakeLifecycleImage.Name()) - - args := fakeImageFetcher.FetchCalls[fakeLifecycleImage.Name()] + h.AssertContains(t, fakeLifecycle.Opts.LifecycleImage, "pack.local/lifecycle") + args := fakeImageFetcher.FetchCalls[origLifecyleName] + h.AssertNotNil(t, args) h.AssertEq(t, args.Daemon, true) h.AssertEq(t, args.PullPolicy, image.PullAlways) h.AssertEq(t, args.Platform, "linux/amd64") @@ -2199,6 +2198,7 @@ api = "0.2" when("builder is untrusted", func() { when("lifecycle image is available", func() { it("uses the 5 phases with the lifecycle image", func() { + origLifecyleName := fakeLifecycleImage.Name() h.AssertNil(t, subject.Build(context.TODO(), BuildOptions{ Image: "some/app", Builder: defaultBuilderName, @@ -2206,9 +2206,9 @@ api = "0.2" TrustBuilder: func(string) bool { return false }, })) h.AssertEq(t, fakeLifecycle.Opts.UseCreator, false) - h.AssertEq(t, fakeLifecycle.Opts.LifecycleImage, fakeLifecycleImage.Name()) - - args := fakeImageFetcher.FetchCalls[fakeLifecycleImage.Name()] + h.AssertContains(t, fakeLifecycle.Opts.LifecycleImage, "pack.local/lifecycle") + args := fakeImageFetcher.FetchCalls[origLifecyleName] + h.AssertNotNil(t, args) h.AssertEq(t, args.Daemon, true) h.AssertEq(t, args.PullPolicy, image.PullAlways) h.AssertEq(t, args.Platform, "linux/amd64") @@ -3013,40 +3013,19 @@ api = "0.2" when("there are extensions", func() { withExtensionsLabel = true - when("experimental", func() { - when("false", func() { - it("errors", func() { - err := subject.Build(context.TODO(), BuildOptions{ - Image: "some/app", - Builder: defaultBuilderName, - }) - - h.AssertNotNil(t, err) - }) - }) - - when("true", func() { - it.Before(func() { - subject.experimental = true + when("default configuration", func() { + it("succeeds", func() { + err := subject.Build(context.TODO(), BuildOptions{ + Image: "some/app", + Builder: defaultBuilderName, }) - it("succeeds", func() { - err := subject.Build(context.TODO(), BuildOptions{ - Image: "some/app", - Builder: defaultBuilderName, - }) - - h.AssertNil(t, err) - h.AssertEq(t, fakeLifecycle.Opts.BuilderImage, defaultBuilderName) - }) + h.AssertNil(t, err) + h.AssertEq(t, fakeLifecycle.Opts.BuilderImage, defaultBuilderName) }) }) when("os", func() { - it.Before(func() { - subject.experimental = true - }) - when("windows", func() { it.Before(func() { h.SkipIf(t, runtime.GOOS != "windows", "Skipped on non-windows") @@ -3076,10 +3055,6 @@ api = "0.2" }) when("pull policy", func() { - it.Before(func() { - subject.experimental = true - }) - when("always", func() { it("succeeds", func() { err := subject.Build(context.TODO(), BuildOptions{ diff --git a/pkg/client/client.go b/pkg/client/client.go index d034851fc6..7dd899b20a 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -52,6 +52,14 @@ type ImageFetcher interface { // PullIfNotPresent and daemon = false, gives us the same behavior as PullAlways. // There is a single invalid configuration, PullNever and daemon = false, this will always fail. Fetch(ctx context.Context, name string, options image.FetchOptions) (imgutil.Image, error) + + // CheckReadAccess verifies if an image is accessible with read permissions + // When FetchOptions.Daemon is true and the image doesn't exist in the daemon, + // the behavior is dictated by the pull policy, which can have the following behavior + // - PullNever: returns false + // - PullAlways Or PullIfNotPresent: it will check read access for the remote image. + // When FetchOptions.Daemon is false it will check read access for the remote image. + CheckReadAccess(repo string, options image.FetchOptions) bool } //go:generate mockgen -package testmocks -destination ../testmocks/mock_blob_downloader.go github.com/buildpacks/pack/pkg/client BlobDownloader @@ -80,13 +88,6 @@ type BuildpackDownloader interface { Download(ctx context.Context, buildpackURI string, opts buildpack.DownloadOptions) (buildpack.BuildModule, []buildpack.BuildModule, error) } -//go:generate mockgen -package testmocks -destination ../testmocks/mock_access_checker.go github.com/buildpacks/pack/pkg/client AccessChecker - -// AccessChecker is an interface for checking remote images for read access -type AccessChecker interface { - Check(repo string) bool -} - // Client is an orchestration object, it contains all parameters needed to // build an app image using Cloud Native Buildpacks. // All settings on this object should be changed through ClientOption functions. @@ -97,7 +98,6 @@ type Client struct { keychain authn.Keychain imageFactory ImageFactory imageFetcher ImageFetcher - accessChecker AccessChecker downloader BlobDownloader lifecycleExecutor LifecycleExecutor buildpackDownloader BuildpackDownloader @@ -133,14 +133,6 @@ func WithFetcher(f ImageFetcher) Option { } } -// WithAccessChecker supply your own AccessChecker. -// A AccessChecker returns true if an image is accessible for reading. -func WithAccessChecker(f AccessChecker) Option { - return func(c *Client) { - c.accessChecker = f - } -} - // WithDownloader supply your own downloader. // A Downloader is used to gather buildpacks from both remote urls, or local sources. func WithDownloader(d BlobDownloader) Option { @@ -241,10 +233,6 @@ func NewClient(opts ...Option) (*Client, error) { } } - if client.accessChecker == nil { - client.accessChecker = image.NewAccessChecker(client.logger, client.keychain) - } - if client.buildpackDownloader == nil { client.buildpackDownloader = buildpack.NewDownloader( client.logger, diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index 2ee6d8b2fe..0b40ec0a8f 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -10,7 +10,6 @@ import ( "github.com/sclevine/spec" "github.com/sclevine/spec/report" - "github.com/buildpacks/pack/pkg/image" "github.com/buildpacks/pack/pkg/logging" "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" @@ -123,13 +122,4 @@ func testClient(t *testing.T, when spec.G, it spec.S) { h.AssertEq(t, cl.registryMirrors, registryMirrors) }) }) - - when("#WithAccessChecker", func() { - it("uses AccessChecker provided", func() { - ac := &image.Checker{} - cl, err := NewClient(WithAccessChecker(ac)) - h.AssertNil(t, err) - h.AssertSameInstance(t, cl.accessChecker, ac) - }) - }) } diff --git a/pkg/client/common.go b/pkg/client/common.go index d3c67d882b..e8e7f07ab8 100644 --- a/pkg/client/common.go +++ b/pkg/client/common.go @@ -10,6 +10,7 @@ import ( "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/internal/registry" "github.com/buildpacks/pack/internal/style" + "github.com/buildpacks/pack/pkg/image" "github.com/buildpacks/pack/pkg/logging" ) @@ -28,7 +29,7 @@ func (c *Client) parseTagReference(imageName string) (name.Reference, error) { return ref, nil } -func (c *Client) resolveRunImage(runImage, imgRegistry, bldrRegistry string, runImageMetadata builder.RunImageMetadata, additionalMirrors map[string][]string, publish bool, accessChecker AccessChecker) string { +func (c *Client) resolveRunImage(runImage, imgRegistry, bldrRegistry string, runImageMetadata builder.RunImageMetadata, additionalMirrors map[string][]string, publish bool, options image.FetchOptions) string { if runImage != "" { c.logger.Debugf("Using provided run-image %s", style.Symbol(runImage)) return runImage @@ -44,7 +45,8 @@ func (c *Client) resolveRunImage(runImage, imgRegistry, bldrRegistry string, run runImageMetadata.Image, runImageMetadata.Mirrors, additionalMirrors[runImageMetadata.Image], - accessChecker, + c.imageFetcher, + options, ) switch { @@ -108,8 +110,8 @@ func contains(slc []string, v string) bool { return false } -func getBestRunMirror(registry string, runImage string, mirrors []string, preferredMirrors []string, accessChecker AccessChecker) string { - runImageList := filterImageList(append(append(append([]string{}, preferredMirrors...), runImage), mirrors...), accessChecker) +func getBestRunMirror(registry string, runImage string, mirrors []string, preferredMirrors []string, fetcher ImageFetcher, options image.FetchOptions) string { + runImageList := filterImageList(append(append(append([]string{}, preferredMirrors...), runImage), mirrors...), fetcher, options) for _, img := range runImageList { ref, err := name.ParseReference(img, name.WeakValidation) if err != nil { @@ -127,11 +129,11 @@ func getBestRunMirror(registry string, runImage string, mirrors []string, prefer return runImage } -func filterImageList(imageList []string, accessChecker AccessChecker) []string { +func filterImageList(imageList []string, fetcher ImageFetcher, options image.FetchOptions) []string { var accessibleImages []string for i, img := range imageList { - if accessChecker.Check(img) { + if fetcher.CheckReadAccess(img, options) { accessibleImages = append(accessibleImages, imageList[i]) } } diff --git a/pkg/client/common_test.go b/pkg/client/common_test.go index 76138b5678..9a14ed0c24 100644 --- a/pkg/client/common_test.go +++ b/pkg/client/common_test.go @@ -4,13 +4,17 @@ import ( "bytes" "testing" + "github.com/buildpacks/lifecycle/auth" + "github.com/golang/mock/gomock" + "github.com/google/go-containerregistry/pkg/authn" "github.com/heroku/color" "github.com/sclevine/spec" "github.com/sclevine/spec/report" "github.com/buildpacks/pack/internal/builder" - ifakes "github.com/buildpacks/pack/internal/fakes" + "github.com/buildpacks/pack/pkg/image" "github.com/buildpacks/pack/pkg/logging" + "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" ) @@ -26,21 +30,25 @@ func testCommon(t *testing.T, when spec.G, it spec.S) { subject *Client outBuf bytes.Buffer logger logging.Logger + keychain authn.Keychain runImageName string defaultRegistry string defaultMirror string gcrRegistry string gcrRunMirror string stackInfo builder.StackMetadata - accessChecker *ifakes.FakeAccessChecker assert = h.NewAssertionManager(t) + publish bool + err error ) it.Before(func() { logger = logging.NewLogWithWriters(&outBuf, &outBuf) - var err error - subject, err = NewClient(WithLogger(logger)) + keychain, err = auth.DefaultKeychain("pack-test/dummy") + h.AssertNil(t, err) + + subject, err = NewClient(WithLogger(logger), WithKeychain(keychain)) assert.Nil(err) defaultRegistry = "default.registry.io" @@ -56,20 +64,32 @@ func testCommon(t *testing.T, when spec.G, it spec.S) { }, }, } - accessChecker = ifakes.NewFakeAccessChecker() }) when("passed specific run image", func() { + it.Before(func() { + publish = false + }) + it("selects that run image", func() { runImgFlag := "flag/passed-run-image" - runImageName := subject.resolveRunImage(runImgFlag, defaultRegistry, "", stackInfo.RunImage, nil, false, accessChecker) + runImageName = subject.resolveRunImage(runImgFlag, defaultRegistry, "", stackInfo.RunImage, nil, publish, image.FetchOptions{Daemon: !publish, PullPolicy: image.PullAlways}) assert.Equal(runImageName, runImgFlag) }) }) - when("publish is true", func() { + when("desirable run-image are accessible", func() { + it.Before(func() { + publish = true + mockController := gomock.NewController(t) + mockFetcher := testmocks.NewMockImageFetcher(mockController) + mockFetcher.EXPECT().CheckReadAccessValidator(gomock.Any(), gomock.Any()).Return(true).AnyTimes() + subject, err = NewClient(WithLogger(logger), WithKeychain(keychain), WithFetcher(mockFetcher)) + h.AssertNil(t, err) + }) + it("defaults to run-image in registry publishing to", func() { - runImageName := subject.resolveRunImage("", gcrRegistry, defaultRegistry, stackInfo.RunImage, nil, true, accessChecker) + runImageName = subject.resolveRunImage("", gcrRegistry, defaultRegistry, stackInfo.RunImage, nil, publish, image.FetchOptions{}) assert.Equal(runImageName, gcrRunMirror) }) @@ -77,7 +97,7 @@ func testCommon(t *testing.T, when spec.G, it spec.S) { configMirrors := map[string][]string{ runImageName: {defaultRegistry + "/unique-run-img"}, } - runImageName := subject.resolveRunImage("", defaultRegistry, "", stackInfo.RunImage, configMirrors, true, accessChecker) + runImageName = subject.resolveRunImage("", defaultRegistry, "", stackInfo.RunImage, configMirrors, publish, image.FetchOptions{}) assert.NotEqual(runImageName, defaultMirror) assert.Equal(runImageName, defaultRegistry+"/unique-run-img") }) @@ -86,54 +106,46 @@ func testCommon(t *testing.T, when spec.G, it spec.S) { configMirrors := map[string][]string{ runImageName: {defaultRegistry + "/unique-run-img"}, } - runImageName := subject.resolveRunImage("", "test.registry.io", "", stackInfo.RunImage, configMirrors, true, accessChecker) + runImageName = subject.resolveRunImage("", "test.registry.io", "", stackInfo.RunImage, configMirrors, publish, image.FetchOptions{}) assert.NotEqual(runImageName, defaultMirror) assert.Equal(runImageName, defaultRegistry+"/unique-run-img") }) }) - // If publish is false, we are using the local daemon, and want to match to the builder registry - when("publish is false", func() { - it("defaults to run-image in registry publishing to", func() { - runImageName := subject.resolveRunImage("", gcrRegistry, defaultRegistry, stackInfo.RunImage, nil, false, accessChecker) - assert.Equal(runImageName, defaultMirror) - assert.NotEqual(runImageName, gcrRunMirror) - }) + when("desirable run-images are not accessible", func() { + it.Before(func() { + publish = true - it("prefers config defined run image mirror to stack defined run image mirror", func() { - configMirrors := map[string][]string{ - runImageName: {defaultRegistry + "/unique-run-img"}, - } - runImageName := subject.resolveRunImage("", gcrRegistry, defaultRegistry, stackInfo.RunImage, configMirrors, false, accessChecker) - assert.NotEqual(runImageName, defaultMirror) - assert.Equal(runImageName, defaultRegistry+"/unique-run-img") + mockController := gomock.NewController(t) + mockFetcher := testmocks.NewMockImageFetcher(mockController) + mockFetcher.EXPECT().CheckReadAccessValidator(gcrRunMirror, gomock.Any()).Return(false) + mockFetcher.EXPECT().CheckReadAccessValidator(stackInfo.RunImage.Image, gomock.Any()).Return(false) + mockFetcher.EXPECT().CheckReadAccessValidator(defaultMirror, gomock.Any()).Return(true) + + subject, err = NewClient(WithLogger(logger), WithKeychain(keychain), WithFetcher(mockFetcher)) + h.AssertNil(t, err) }) - it("returns a config mirror if no match to target registry", func() { - configMirrors := map[string][]string{ - runImageName: {defaultRegistry + "/unique-run-img"}, - } - runImageName := subject.resolveRunImage("", defaultRegistry, "test.registry.io", stackInfo.RunImage, configMirrors, false, accessChecker) - assert.NotEqual(runImageName, defaultMirror) - assert.Equal(runImageName, defaultRegistry+"/unique-run-img") + it("selects the first accessible run-image", func() { + runImageName = subject.resolveRunImage("", gcrRegistry, defaultRegistry, stackInfo.RunImage, nil, publish, image.FetchOptions{}) + assert.Equal(runImageName, defaultMirror) }) }) - when("desirable run-image is not accessible", func() { + when("desirable run-image are empty", func() { it.Before(func() { - accessChecker.RegistriesToFail = []string{ - gcrRunMirror, - stackInfo.RunImage.Image, + publish = false + stackInfo = builder.StackMetadata{ + RunImage: builder.RunImageMetadata{ + Image: "stack/run-image", + }, } }) - it.After(func() { - accessChecker.RegistriesToFail = nil - }) - - it("selects the first accessible run-image", func() { - runImageName := subject.resolveRunImage("", gcrRegistry, defaultRegistry, stackInfo.RunImage, nil, true, accessChecker) - assert.Equal(runImageName, defaultMirror) + it("selects the builder run-image", func() { + // issue: https://github.com/buildpacks/pack/issues/2078 + runImageName = subject.resolveRunImage("", "", "", stackInfo.RunImage, nil, publish, image.FetchOptions{}) + assert.Equal(runImageName, "stack/run-image") }) }) }) diff --git a/pkg/client/create_builder_test.go b/pkg/client/create_builder_test.go index 7c51fa3090..88e888d9c6 100644 --- a/pkg/client/create_builder_test.go +++ b/pkg/client/create_builder_test.go @@ -112,10 +112,10 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { mockDownloader.EXPECT().Download(gomock.Any(), "file:///some-lifecycle").Return(blob.NewBlob(filepath.Join("testdata", "lifecycle", "platform-0.4")), nil).AnyTimes() mockDownloader.EXPECT().Download(gomock.Any(), "file:///some-lifecycle-platform-0-1").Return(blob.NewBlob(filepath.Join("testdata", "lifecycle-platform-0.1")), nil).AnyTimes() - bp, err := buildpack.FromBuildpackRootBlob(exampleBuildpackBlob, archive.DefaultTarWriterFactory()) + bp, err := buildpack.FromBuildpackRootBlob(exampleBuildpackBlob, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) mockBuildpackDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/bp-one.tgz", gomock.Any()).Return(bp, nil, nil).AnyTimes() - ext, err := buildpack.FromExtensionRootBlob(exampleExtensionBlob, archive.DefaultTarWriterFactory()) + ext, err := buildpack.FromExtensionRootBlob(exampleExtensionBlob, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) mockBuildpackDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/ext-one.tgz", gomock.Any()).Return(ext, nil, nil).AnyTimes() @@ -776,12 +776,12 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { opts.Config.Extensions[0].URI = "https://example.fake/ext-one-with-api-9.tgz" buildpackBlob := blob.NewBlob(filepath.Join("testdata", "buildpack-api-0.4")) - bp, err := buildpack.FromBuildpackRootBlob(buildpackBlob, archive.DefaultTarWriterFactory()) + bp, err := buildpack.FromBuildpackRootBlob(buildpackBlob, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) mockBuildpackDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/bp-one-with-api-4.tgz", gomock.Any()).Return(bp, nil, nil) extensionBlob := blob.NewBlob(filepath.Join("testdata", "extension-api-0.9")) - extension, err := buildpack.FromExtensionRootBlob(extensionBlob, archive.DefaultTarWriterFactory()) + extension, err := buildpack.FromExtensionRootBlob(extensionBlob, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) mockBuildpackDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/ext-one-with-api-9.tgz", gomock.Any()).Return(extension, nil, nil) @@ -824,16 +824,16 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { bp2v1Blob := blob.NewBlob(filepath.Join("testdata", "buildpack-non-deterministic", "buildpack-2-version-1")) bp2v2Blob := blob.NewBlob(filepath.Join("testdata", "buildpack-non-deterministic", "buildpack-2-version-2")) - bp1v1, err = buildpack.FromBuildpackRootBlob(bp1v1Blob, archive.DefaultTarWriterFactory()) + bp1v1, err = buildpack.FromBuildpackRootBlob(bp1v1Blob, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) - bp1v2, err = buildpack.FromBuildpackRootBlob(bp1v2Blob, archive.DefaultTarWriterFactory()) + bp1v2, err = buildpack.FromBuildpackRootBlob(bp1v2Blob, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) - bp2v1, err = buildpack.FromBuildpackRootBlob(bp2v1Blob, archive.DefaultTarWriterFactory()) + bp2v1, err = buildpack.FromBuildpackRootBlob(bp2v1Blob, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) - bp2v2, err = buildpack.FromBuildpackRootBlob(bp2v2Blob, archive.DefaultTarWriterFactory()) + bp2v2, err = buildpack.FromBuildpackRootBlob(bp2v2Blob, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) return []buildpack.BuildModule{bp2v2, bp2v1, bp1v1, bp1v2} @@ -855,7 +855,7 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { bpDependencies := prepareBuildpackDependencies() buildpackBlob := blob.NewBlob(filepath.Join("testdata", "buildpack-api-0.4")) - bp, err := buildpack.FromBuildpackRootBlob(buildpackBlob, archive.DefaultTarWriterFactory()) + bp, err := buildpack.FromBuildpackRootBlob(buildpackBlob, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) mockBuildpackDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/bp-one-with-api-4.tgz", gomock.Any()).DoAndReturn( func(ctx context.Context, buildpackURI string, opts buildpack.DownloadOptions) (buildpack.BuildModule, []buildpack.BuildModule, error) { @@ -865,7 +865,7 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { }) extensionBlob := blob.NewBlob(filepath.Join("testdata", "extension-api-0.9")) - extension, err := buildpack.FromExtensionRootBlob(extensionBlob, archive.DefaultTarWriterFactory()) + extension, err := buildpack.FromExtensionRootBlob(extensionBlob, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) mockBuildpackDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/ext-one-with-api-9.tgz", gomock.Any()).DoAndReturn( func(ctx context.Context, buildpackURI string, opts buildpack.DownloadOptions) (buildpack.BuildModule, []buildpack.BuildModule, error) { @@ -898,7 +898,7 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { opts.Config.Buildpacks[0].URI = directoryPath buildpackBlob := blob.NewBlob(directoryPath) - buildpack, err := buildpack.FromBuildpackRootBlob(buildpackBlob, archive.DefaultTarWriterFactory()) + buildpack, err := buildpack.FromBuildpackRootBlob(buildpackBlob, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) mockBuildpackDownloader.EXPECT().Download(gomock.Any(), directoryPath, gomock.Any()).Return(buildpack, nil, nil) @@ -914,7 +914,7 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { opts.Config.Extensions[0].URI = directoryPath extensionBlob := blob.NewBlob(directoryPath) - extension, err := buildpack.FromExtensionRootBlob(extensionBlob, archive.DefaultTarWriterFactory()) + extension, err := buildpack.FromExtensionRootBlob(extensionBlob, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) mockBuildpackDownloader.EXPECT().Download(gomock.Any(), directoryPath, gomock.Any()).Return(extension, nil, nil) @@ -1030,13 +1030,13 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { blob1 := blob.NewBlob(filepath.Join("testdata", "buildpack-flatten", "buildpack-1")) for i := 2; i <= 7; i++ { b := blob.NewBlob(filepath.Join("testdata", "buildpack-flatten", fmt.Sprintf("buildpack-%d", i))) - bp, err := buildpack.FromBuildpackRootBlob(b, archive.DefaultTarWriterFactory()) + bp, err := buildpack.FromBuildpackRootBlob(b, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) depBPs = append(depBPs, bp) } mockDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/flatten-bp-1.tgz").Return(blob1, nil).AnyTimes() - bp, err := buildpack.FromBuildpackRootBlob(blob1, archive.DefaultTarWriterFactory()) + bp, err := buildpack.FromBuildpackRootBlob(blob1, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) mockBuildpackDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/flatten-bp-1.tgz", gomock.Any()).Return(bp, depBPs, nil).AnyTimes() diff --git a/pkg/client/example_fetcher_test.go b/pkg/client/example_fetcher_test.go index e8cf56ce65..d649e842d7 100644 --- a/pkg/client/example_fetcher_test.go +++ b/pkg/client/example_fetcher_test.go @@ -53,3 +53,7 @@ func (f *fetcher) Fetch(_ context.Context, imageName string, _ image.FetchOption fmt.Println("custom fetcher called") return nil, errors.New("not implemented") } + +func (f *fetcher) CheckReadAccess(_ string, _ FetchOptions) bool { + return true +} diff --git a/pkg/client/package_buildpack.go b/pkg/client/package_buildpack.go index 49ca63882a..2cdcddb9ec 100644 --- a/pkg/client/package_buildpack.go +++ b/pkg/client/package_buildpack.go @@ -98,7 +98,7 @@ func (c *Client) PackageBuildpack(ctx context.Context, opts PackageBuildpackOpti return err } - bp, err := buildpack.FromBuildpackRootBlob(mainBlob, writerFactory) + bp, err := buildpack.FromBuildpackRootBlob(mainBlob, writerFactory, c.logger) if err != nil { return errors.Wrapf(err, "creating buildpack from %s", style.Symbol(bpURI)) } diff --git a/pkg/client/package_buildpack_test.go b/pkg/client/package_buildpack_test.go index b94662c7de..1810eb418a 100644 --- a/pkg/client/package_buildpack_test.go +++ b/pkg/client/package_buildpack_test.go @@ -462,25 +462,25 @@ func testPackageBuildpack(t *testing.T, when spec.G, it spec.S) { blob1 := blob.NewBlob(filepath.Join("testdata", "buildpack-flatten", "buildpack-1")) mockDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/flatten-bp-1.tgz").Return(blob1, nil).AnyTimes() - bp, err := buildpack.FromBuildpackRootBlob(blob1, archive.DefaultTarWriterFactory()) + bp, err := buildpack.FromBuildpackRootBlob(blob1, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) mockBuildpackDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/flatten-bp-1.tgz", gomock.Any()).Return(bp, nil, nil).AnyTimes() // flatten buildpack 2 blob2 := blob.NewBlob(filepath.Join("testdata", "buildpack-flatten", "buildpack-2")) - bp2, err := buildpack.FromBuildpackRootBlob(blob2, archive.DefaultTarWriterFactory()) + bp2, err := buildpack.FromBuildpackRootBlob(blob2, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) mockBuildpackDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/flatten-bp-2.tgz", gomock.Any()).Return(bp2, nil, nil).AnyTimes() // flatten buildpack 3 blob3 := blob.NewBlob(filepath.Join("testdata", "buildpack-flatten", "buildpack-3")) - bp3, err := buildpack.FromBuildpackRootBlob(blob3, archive.DefaultTarWriterFactory()) + bp3, err := buildpack.FromBuildpackRootBlob(blob3, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) var depBPs []buildpack.BuildModule for i := 4; i <= 7; i++ { b := blob.NewBlob(filepath.Join("testdata", "buildpack-flatten", fmt.Sprintf("buildpack-%d", i))) - bp, err := buildpack.FromBuildpackRootBlob(b, archive.DefaultTarWriterFactory()) + bp, err := buildpack.FromBuildpackRootBlob(b, archive.DefaultTarWriterFactory(), nil) h.AssertNil(t, err) depBPs = append(depBPs, bp) } diff --git a/pkg/client/package_extension.go b/pkg/client/package_extension.go index 0e2c0e7317..690d12afba 100644 --- a/pkg/client/package_extension.go +++ b/pkg/client/package_extension.go @@ -42,7 +42,7 @@ func (c *Client) PackageExtension(ctx context.Context, opts PackageBuildpackOpti return err } - ex, err := buildpack.FromExtensionRootBlob(mainBlob, writerFactory) + ex, err := buildpack.FromExtensionRootBlob(mainBlob, writerFactory, c.logger) if err != nil { return errors.Wrapf(err, "creating extension from %s", style.Symbol(exURI)) } diff --git a/pkg/client/rebase.go b/pkg/client/rebase.go index f493f6184a..3d7e6532da 100644 --- a/pkg/client/rebase.go +++ b/pkg/client/rebase.go @@ -98,6 +98,13 @@ func (c *Client) Rebase(ctx context.Context, opts RebaseOptions) error { Mirrors: md.Stack.RunImage.Mirrors, } } + + fetchOptions := image.FetchOptions{ + Daemon: !opts.Publish, + PullPolicy: opts.PullPolicy, + Platform: fmt.Sprintf("%s/%s", appOS, appArch), + } + runImageName := c.resolveRunImage( opts.RunImage, imageRef.Context().RegistryStr(), @@ -105,18 +112,14 @@ func (c *Client) Rebase(ctx context.Context, opts RebaseOptions) error { runImageMD, opts.AdditionalMirrors, opts.Publish, - c.accessChecker, + fetchOptions, ) if runImageName == "" { return errors.New("run image must be specified") } - baseImage, err := c.imageFetcher.Fetch(ctx, runImageName, image.FetchOptions{ - Daemon: !opts.Publish, - PullPolicy: opts.PullPolicy, - Platform: fmt.Sprintf("%s/%s", appOS, appArch), - }) + baseImage, err := c.imageFetcher.Fetch(ctx, runImageName, fetchOptions) if err != nil { return err } diff --git a/pkg/client/rebase_test.go b/pkg/client/rebase_test.go index ba6a9df86b..7ceff08df2 100644 --- a/pkg/client/rebase_test.go +++ b/pkg/client/rebase_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/buildpacks/imgutil/fakes" + "github.com/buildpacks/lifecycle/auth" "github.com/heroku/color" "github.com/sclevine/spec" "github.com/sclevine/spec/report" @@ -28,7 +29,6 @@ func testRebase(t *testing.T, when spec.G, it spec.S) { when("#Rebase", func() { var ( fakeImageFetcher *ifakes.FakeImageFetcher - fakeAccessChecker *ifakes.FakeAccessChecker subject *Client fakeAppImage *fakes.Image fakeRunImage *fakes.Image @@ -38,7 +38,6 @@ func testRebase(t *testing.T, when spec.G, it spec.S) { it.Before(func() { fakeImageFetcher = ifakes.NewFakeImageFetcher() - fakeAccessChecker = ifakes.NewFakeAccessChecker() fakeAppImage = fakes.NewImage("some/app", "", &fakeIdentifier{name: "app-image"}) h.AssertNil(t, fakeAppImage.SetLabel("io.buildpacks.lifecycle.metadata", @@ -54,11 +53,14 @@ func testRebase(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, fakeRunImageMirror.SetLabel("io.buildpacks.stack.id", "io.buildpacks.stacks.jammy")) fakeImageFetcher.LocalImages["example.com/some/run"] = fakeRunImageMirror + keychain, err := auth.DefaultKeychain("pack-test/dummy") + h.AssertNil(t, err) + fakeLogger := logging.NewLogWithWriters(&out, &out) subject = &Client{ - logger: fakeLogger, - imageFetcher: fakeImageFetcher, - accessChecker: fakeAccessChecker, + logger: fakeLogger, + imageFetcher: fakeImageFetcher, + keychain: keychain, } }) diff --git a/pkg/dist/buildmodule.go b/pkg/dist/buildmodule.go index 5126d988e0..ea34cb68ca 100644 --- a/pkg/dist/buildmodule.go +++ b/pkg/dist/buildmodule.go @@ -56,10 +56,10 @@ type Target struct { OS string `json:"os" toml:"os"` Arch string `json:"arch" toml:"arch"` ArchVariant string `json:"variant,omitempty" toml:"variant,omitempty"` - Distributions []Distribution `json:"distributions,omitempty" toml:"distributions,omitempty"` + Distributions []Distribution `json:"distros,omitempty" toml:"distros,omitempty"` } type Distribution struct { - Name string `json:"name,omitempty" toml:"name,omitempty"` - Versions []string `json:"versions,omitempty" toml:"versions,omitempty"` + Name string `json:"name,omitempty" toml:"name,omitempty"` + Version string `json:"version,omitempty" toml:"version,omitempty"` } diff --git a/pkg/dist/buildpack_descriptor.go b/pkg/dist/buildpack_descriptor.go index cf93daa2a2..77ed9028d4 100644 --- a/pkg/dist/buildpack_descriptor.go +++ b/pkg/dist/buildpack_descriptor.go @@ -54,32 +54,25 @@ func (b *BuildpackDescriptor) EnsureStackSupport(stackID string, providedMixins return nil } -func (b *BuildpackDescriptor) EnsureTargetSupport(os, arch, distroName, distroVersion string) error { +func (b *BuildpackDescriptor) EnsureTargetSupport(givenOS, givenArch, givenDistroName, givenDistroVersion string) error { if len(b.Targets()) == 0 { if (!b.WithLinuxBuild && !b.WithWindowsBuild) || len(b.Stacks()) > 0 { // nolint return nil // Order buildpack or stack buildpack, no validation required - } else if b.WithLinuxBuild && os == DefaultTargetOSLinux && arch == DefaultTargetArch { + } else if b.WithLinuxBuild && givenOS == DefaultTargetOSLinux && givenArch == DefaultTargetArch { return nil - } else if b.WithWindowsBuild && os == DefaultTargetOSWindows && arch == DefaultTargetArch { + } else if b.WithWindowsBuild && givenOS == DefaultTargetOSWindows && givenArch == DefaultTargetArch { return nil } } - for _, target := range b.Targets() { - if target.OS == os { - if target.Arch == "" || arch == "" || target.Arch == arch { - if len(target.Distributions) == 0 || distroName == "" || distroVersion == "" { + for _, bpTarget := range b.Targets() { + if bpTarget.OS == givenOS { + if bpTarget.Arch == "" || givenArch == "" || bpTarget.Arch == givenArch { + if len(bpTarget.Distributions) == 0 || givenDistroName == "" || givenDistroVersion == "" { return nil } - for _, distro := range target.Distributions { - if distro.Name == distroName { - if len(distro.Versions) == 0 { - return nil - } - for _, version := range distro.Versions { - if version == distroVersion { - return nil - } - } + for _, bpDistro := range bpTarget.Distributions { + if bpDistro.Name == givenDistroName && bpDistro.Version == givenDistroVersion { + return nil } } } @@ -97,9 +90,9 @@ func (b *BuildpackDescriptor) EnsureTargetSupport(os, arch, distroName, distroVe return fmt.Errorf( "unable to satisfy target os/arch constraints; build image: %s, buildpack %s: %s", toJSONMaybe(target{ - OS: os, - Arch: arch, - Distribution: osDistribution{Name: distroName, Version: distroVersion}, + OS: givenOS, + Arch: givenArch, + Distribution: osDistribution{Name: givenDistroName, Version: givenDistroVersion}, }), style.Symbol(b.Info().FullName()), toJSONMaybe(b.Targets()), diff --git a/pkg/dist/buildpack_descriptor_test.go b/pkg/dist/buildpack_descriptor_test.go index 1f755edb5d..99f2e91124 100644 --- a/pkg/dist/buildpack_descriptor_test.go +++ b/pkg/dist/buildpack_descriptor_test.go @@ -221,12 +221,12 @@ func testBuildpackDescriptor(t *testing.T, when spec.G, it spec.S) { Arch: "fake-arch", Distributions: []dist.Distribution{ { - Name: "fake-distro", - Versions: []string{"0.1"}, + Name: "fake-distro", + Version: "0.1", }, { - Name: "another-distro", - Versions: []string{"0.22"}, + Name: "another-distro", + Version: "0.22", }, }, }}, @@ -247,12 +247,12 @@ func testBuildpackDescriptor(t *testing.T, when spec.G, it spec.S) { Arch: "fake-arch", Distributions: []dist.Distribution{ { - Name: "fake-distro", - Versions: []string{"0.1"}, + Name: "fake-distro", + Version: "0.1", }, { - Name: "another-distro", - Versions: []string{"0.22"}, + Name: "another-distro", + Version: "0.22", }, }, }}, @@ -260,7 +260,7 @@ func testBuildpackDescriptor(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, bp.EnsureStackSupport("some.stack.id", []string{}, true)) h.AssertError(t, bp.EnsureTargetSupport("some-other-os", "fake-arch", "fake-distro", "0.0"), - `unable to satisfy target os/arch constraints; build image: {"os":"some-other-os","arch":"fake-arch","distribution":{"name":"fake-distro","version":"0.0"}}, buildpack 'some.buildpack.id@some.buildpack.version': [{"os":"fake-os","arch":"fake-arch","distributions":[{"name":"fake-distro","versions":["0.1"]},{"name":"another-distro","versions":["0.22"]}]}]`) + `unable to satisfy target os/arch constraints; build image: {"os":"some-other-os","arch":"fake-arch","distribution":{"name":"fake-distro","version":"0.0"}}, buildpack 'some.buildpack.id@some.buildpack.version': [{"os":"fake-os","arch":"fake-arch","distros":[{"name":"fake-distro","version":"0.1"},{"name":"another-distro","version":"0.22"}]}]`) }) it("succeeds with missing arch", func() { diff --git a/pkg/image/access_checker.go b/pkg/image/access_checker.go deleted file mode 100644 index bc9ae55cd0..0000000000 --- a/pkg/image/access_checker.go +++ /dev/null @@ -1,41 +0,0 @@ -package image - -import ( - "github.com/buildpacks/imgutil/remote" - "github.com/google/go-containerregistry/pkg/authn" - - "github.com/buildpacks/pack/pkg/logging" -) - -type Checker struct { - logger logging.Logger - keychain authn.Keychain -} - -func NewAccessChecker(logger logging.Logger, keychain authn.Keychain) *Checker { - checker := &Checker{ - logger: logger, - keychain: keychain, - } - - if checker.keychain == nil { - checker.keychain = authn.DefaultKeychain - } - - return checker -} - -func (c *Checker) Check(repo string) bool { - img, err := remote.NewImage(repo, c.keychain) - if err != nil { - return false - } - - if ok, err := img.CheckReadAccess(); ok { - c.logger.Debugf("CheckReadAccess succeeded for the run image %s", repo) - return true - } else { - c.logger.Debugf("CheckReadAccess failed for the run image %s, error: %s", repo, err.Error()) - return false - } -} diff --git a/pkg/image/access_checker_test.go b/pkg/image/access_checker_test.go deleted file mode 100644 index 9b1bf3ebd6..0000000000 --- a/pkg/image/access_checker_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package image_test - -import ( - "bytes" - "testing" - - "github.com/buildpacks/lifecycle/auth" - "github.com/sclevine/spec" - "github.com/sclevine/spec/report" - - "github.com/buildpacks/pack/pkg/image" - "github.com/buildpacks/pack/pkg/logging" - h "github.com/buildpacks/pack/testhelpers" -) - -func TestChecker(t *testing.T) { - spec.Run(t, "Checker", testChecker, spec.Report(report.Terminal{})) -} - -func testChecker(t *testing.T, when spec.G, it spec.S) { - when("#Check", func() { - it("fails when checking dummy image", func() { - buf := &bytes.Buffer{} - - keychain, err := auth.DefaultKeychain("pack.test/dummy") - h.AssertNil(t, err) - - ic := image.NewAccessChecker(logging.NewSimpleLogger(buf), keychain) - - h.AssertFalse(t, ic.Check("pack.test/dummy")) - h.AssertContains(t, buf.String(), "DEBUG: CheckReadAccess failed for the run image pack.test/dummy") - }) - }) -} diff --git a/pkg/image/fetcher.go b/pkg/image/fetcher.go index 60548fcc1d..9b8d0886b2 100644 --- a/pkg/image/fetcher.go +++ b/pkg/image/fetcher.go @@ -123,6 +123,41 @@ func (f *Fetcher) Fetch(ctx context.Context, name string, options FetchOptions) return f.fetchDaemonImage(name) } +func (f *Fetcher) CheckReadAccess(repo string, options FetchOptions) bool { + if !options.Daemon || options.PullPolicy == PullAlways { + return f.checkRemoteReadAccess(repo) + } + if _, err := f.fetchDaemonImage(repo); err != nil { + if errors.Is(err, ErrNotFound) { + // Image doesn't exist in the daemon + // Pull Never: should fail + // Pull If Not Present: need to check the registry + if options.PullPolicy == PullNever { + return false + } + return f.checkRemoteReadAccess(repo) + } + f.logger.Debugf("failed reading image '%s' from the daemon, error: %s", repo, err.Error()) + return false + } + return true +} + +func (f *Fetcher) checkRemoteReadAccess(repo string) bool { + img, err := remote.NewImage(repo, f.keychain) + if err != nil { + f.logger.Debugf("failed accessing remote image %s, error: %s", repo, err.Error()) + return false + } + if ok, err := img.CheckReadAccess(); ok { + f.logger.Debugf("CheckReadAccess succeeded for the run image %s", repo) + return true + } else { + f.logger.Debugf("CheckReadAccess failed for the run image %s, error: %s", repo, err.Error()) + return false + } +} + func (f *Fetcher) fetchDaemonImage(name string) (imgutil.Image, error) { image, err := local.NewImage(name, f.docker, local.FromBaseImage(name)) if err != nil { diff --git a/pkg/image/fetcher_test.go b/pkg/image/fetcher_test.go index 3b119e15f3..b9491b24c2 100644 --- a/pkg/image/fetcher_test.go +++ b/pkg/image/fetcher_test.go @@ -10,17 +10,20 @@ import ( "testing" "github.com/buildpacks/imgutil" - "github.com/buildpacks/imgutil/local" "github.com/buildpacks/imgutil/remote" + "github.com/docker/docker/api/types" "github.com/docker/docker/client" + "github.com/golang/mock/gomock" "github.com/google/go-containerregistry/pkg/authn" "github.com/heroku/color" + "github.com/pkg/errors" "github.com/sclevine/spec" "github.com/sclevine/spec/report" "github.com/buildpacks/pack/pkg/image" "github.com/buildpacks/pack/pkg/logging" + "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" ) @@ -42,7 +45,7 @@ func TestFetcher(t *testing.T) { var err error docker, err = client.NewClientWithOpts(client.FromEnv, client.WithVersion("1.38")) h.AssertNil(t, err) - spec.Run(t, "Fetcher", testFetcher, spec.Report(report.Terminal{})) + spec.Run(t, "Fetcher", testFetcher, spec.Parallel(), spec.Report(report.Terminal{})) } func testFetcher(t *testing.T, when spec.G, it spec.S) { @@ -57,7 +60,7 @@ func testFetcher(t *testing.T, when spec.G, it spec.S) { it.Before(func() { repo = "some-org/" + h.RandString(10) repoName = registryConfig.RepoName(repo) - imageFetcher = image.NewFetcher(logging.NewLogWithWriters(&outBuf, &outBuf), docker) + imageFetcher = image.NewFetcher(logging.NewLogWithWriters(&outBuf, &outBuf, logging.WithVerbose()), docker) info, err := docker.Info(context.TODO()) h.AssertNil(t, err) @@ -404,4 +407,112 @@ func testFetcher(t *testing.T, when spec.G, it spec.S) { }) }) }) + + when("#CheckReadAccess", func() { + var daemon bool + + when("Daemon is true", func() { + it.Before(func() { + daemon = true + }) + + when("an error is thrown by the daemon", func() { + it.Before(func() { + mockController := gomock.NewController(t) + mockDockerClient := testmocks.NewMockCommonAPIClient(mockController) + mockDockerClient.EXPECT().ServerVersion(gomock.Any()).Return(types.Version{}, errors.New("something wrong happened")) + imageFetcher = image.NewFetcher(logging.NewLogWithWriters(&outBuf, &outBuf, logging.WithVerbose()), mockDockerClient) + }) + when("PullNever", func() { + it("read access must be false", func() { + h.AssertFalse(t, imageFetcher.CheckReadAccess("pack.test/dummy", image.FetchOptions{Daemon: daemon, PullPolicy: image.PullNever})) + h.AssertContains(t, outBuf.String(), "failed reading image 'pack.test/dummy' from the daemon") + }) + }) + + when("PullIfNotPresent", func() { + it("read access must be false", func() { + h.AssertFalse(t, imageFetcher.CheckReadAccess("pack.test/dummy", image.FetchOptions{Daemon: daemon, PullPolicy: image.PullIfNotPresent})) + h.AssertContains(t, outBuf.String(), "failed reading image 'pack.test/dummy' from the daemon") + }) + }) + }) + + when("image exists only in the daemon", func() { + it.Before(func() { + img, err := local.NewImage("pack.test/dummy", docker) + h.AssertNil(t, err) + h.AssertNil(t, img.Save()) + }) + when("PullAlways", func() { + it("read access must be false", func() { + h.AssertFalse(t, imageFetcher.CheckReadAccess("pack.test/dummy", image.FetchOptions{Daemon: daemon, PullPolicy: image.PullAlways})) + }) + }) + + when("PullNever", func() { + it("read access must be true", func() { + h.AssertTrue(t, imageFetcher.CheckReadAccess("pack.test/dummy", image.FetchOptions{Daemon: daemon, PullPolicy: image.PullNever})) + }) + }) + + when("PullIfNotPresent", func() { + it("read access must be true", func() { + h.AssertTrue(t, imageFetcher.CheckReadAccess("pack.test/dummy", image.FetchOptions{Daemon: daemon, PullPolicy: image.PullIfNotPresent})) + }) + }) + }) + + when("image doesn't exist in the daemon but in remote", func() { + it.Before(func() { + img, err := remote.NewImage(repoName, authn.DefaultKeychain) + h.AssertNil(t, err) + h.AssertNil(t, img.Save()) + }) + when("PullAlways", func() { + it("read access must be true", func() { + h.AssertTrue(t, imageFetcher.CheckReadAccess(repoName, image.FetchOptions{Daemon: daemon, PullPolicy: image.PullAlways})) + }) + }) + + when("PullNever", func() { + it("read access must be false", func() { + h.AssertFalse(t, imageFetcher.CheckReadAccess(repoName, image.FetchOptions{Daemon: daemon, PullPolicy: image.PullNever})) + }) + }) + + when("PullIfNotPresent", func() { + it("read access must be true", func() { + h.AssertTrue(t, imageFetcher.CheckReadAccess(repoName, image.FetchOptions{Daemon: daemon, PullPolicy: image.PullIfNotPresent})) + }) + }) + }) + }) + + when("Daemon is false", func() { + it.Before(func() { + daemon = false + }) + + when("remote image doesn't exists", func() { + it("fails when checking dummy image", func() { + h.AssertFalse(t, imageFetcher.CheckReadAccess("pack.test/dummy", image.FetchOptions{Daemon: daemon})) + h.AssertContains(t, outBuf.String(), "CheckReadAccess failed for the run image pack.test/dummy") + }) + }) + + when("remote image exists", func() { + it.Before(func() { + img, err := remote.NewImage(repoName, authn.DefaultKeychain) + h.AssertNil(t, err) + h.AssertNil(t, img.Save()) + }) + + it("read access is valid", func() { + h.AssertTrue(t, imageFetcher.CheckReadAccess(repoName, image.FetchOptions{Daemon: daemon})) + h.AssertContains(t, outBuf.String(), fmt.Sprintf("CheckReadAccess succeeded for the run image %s", repoName)) + }) + }) + }) + }) } diff --git a/pkg/testmocks/mock_image_fetcher.go b/pkg/testmocks/mock_image_fetcher.go index 281f28d04d..6fd890db2b 100644 --- a/pkg/testmocks/mock_image_fetcher.go +++ b/pkg/testmocks/mock_image_fetcher.go @@ -37,6 +37,20 @@ func (m *MockImageFetcher) EXPECT() *MockImageFetcherMockRecorder { return m.recorder } +// CheckReadAccessValidator mocks base method. +func (m *MockImageFetcher) CheckReadAccess(arg0 string, arg1 image.FetchOptions) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckReadAccess", arg0, arg1) + ret0, _ := ret[0].(bool) + return ret0 +} + +// CheckReadAccessValidator indicates an expected call of CheckReadAccessValidator. +func (mr *MockImageFetcherMockRecorder) CheckReadAccessValidator(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckReadAccess", reflect.TypeOf((*MockImageFetcher)(nil).CheckReadAccess), arg0, arg1) +} + // Fetch mocks base method. func (m *MockImageFetcher) Fetch(arg0 context.Context, arg1 string, arg2 image.FetchOptions) (imgutil.Image, error) { m.ctrl.T.Helper()