Repository: buildpacks/lifecycle
Pull request #1228 (2023-10-27 09:54:46)

Group minor/patch version Go Dependabot updates into one PR
Go minor/patch dependencies will now be grouped, using the new Dependabot grouping feature:
https://github.blog/changelog/2023-08-17-grouped-version-updates-by-semantic-version-level-for-dependabot/

Major updates, as well as security updates will still be opened as separate PRs. I've not grouped GitHub Actions update PRs, since the volume is typically much lower for those.

The custom open pull requests limit has been removed, since it is no longer necessary, as there won't be as many open PRs.

In addition, the schedule has been changed from daily to weekly.

This reduces project maintenance toil (no more having to manually create combined update PRs), plus makes it less painful for contributors to subscribe to repository notifications (currently there is a lot of noise from Dependabot PRs being opened/auto-rebased etc).


Patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 6798cb20..d83cd867 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -3,9 +3,14 @@ updates:
 - package-ecosystem: gomod
   directory: "/"
   schedule:
-    interval: daily
-  open-pull-requests-limit: 10
+    interval: weekly
+  groups:
+    # Group all minor/patch go dependencies into a single PR.
+    go-dependencies:
+      update-types:
+        - "minor"
+        - "patch"
 - package-ecosystem: "github-actions"
   directory: "/"
   schedule:
-    interval: daily
+    interval: weekly
diff --git a/log/timelog.go b/log/timelog.go
index 3c168e82..b3fcd3b2 100644
--- a/log/timelog.go
+++ b/log/timelog.go
@@ -22,12 +22,12 @@ func NewMeasurement(funcName string, lager Logger) func() {
 // RecordStart grabs the current time and logs it, but it will be called for you if you use the NewMeasurement convenience function.
 func (c *Chronit) RecordStart() {
 	c.StartTime = time.Now()
-	c.Log.Infof("Timer: %s started at %s", c.FunctionName, c.StartTime.Format(time.RFC3339))
+	c.Log.Debugf("Timer: %s started at %s", c.FunctionName, c.StartTime.Format(time.RFC3339))
 }
 
 // RecordEnd is called in the function returned by NewMeasurement.
 // the EndTime will be populated just in case you'll keep the object in scope for later.
 func (c *Chronit) RecordEnd() {
 	c.EndTime = time.Now()
-	c.Log.Infof("Timer: %s ran for %v and ended at %s", c.FunctionName, c.EndTime.Sub(c.StartTime), c.EndTime.Format(time.RFC3339))
+	c.Log.Debugf("Timer: %s ran for %v and ended at %s", c.FunctionName, c.EndTime.Sub(c.StartTime), c.EndTime.Format(time.RFC3339))
 }
diff --git a/phase/save.go b/phase/save.go
index b2e4a891..92f36b8e 100644
--- a/phase/save.go
+++ b/phase/save.go
@@ -20,6 +20,7 @@ func saveImageAs(image imgutil.Image, name string, additionalNames []string, log
 	defer log.NewMeasurement("Saving "+name+"...", logger)()
 	var saveErr error
 	imageReport := files.ImageReport{}
+	logger.Infof("Saving %s...\n", name)
 	if err := image.SaveAs(name, additionalNames...); err != nil {
 		var ok bool
 		if saveErr, ok = err.(imgutil.SaveError); !ok {
diff --git a/platform/resolve_inputs.go b/platform/resolve_inputs.go
index 743784ee..68d633be 100644
--- a/platform/resolve_inputs.go
+++ b/platform/resolve_inputs.go
@@ -11,13 +11,18 @@ import (
 )
 
 var (
-	ErrOutputImageRequired           = "image argument is required"
-	ErrRunImageRequiredWhenNoStackMD = "-run-image is required when there is no stack metadata available"
-	ErrRunImageRequiredWhenNoRunMD   = "-run-image is required when there is no run metadata available"
-	ErrSupplyOnlyOneRunImage         = "supply only one of -run-image or (deprecated) -image"
-	ErrRunImageUnsupported           = "-run-image is unsupported"
-	ErrImageUnsupported              = "-image is unsupported"
-	MsgIgnoringLaunchCache           = "Ignoring -launch-cache, only intended for use with -daemon"
+	// ErrOutputImageRequired user facing error message
+	ErrOutputImageRequired = "image argument is required"
+	// ErrRunImageRequiredWhenNoRunMD user facing error message
+	ErrRunImageRequiredWhenNoRunMD = "-run-image is required when there is no run metadata available"
+	// ErrSupplyOnlyOneRunImage user facing error message
+	ErrSupplyOnlyOneRunImage = "supply only one of -run-image or (deprecated) -image"
+	// ErrRunImageUnsupported user facing error message
+	ErrRunImageUnsupported = "-run-image is unsupported"
+	// ErrImageUnsupported user facing error message
+	ErrImageUnsupported = "-image is unsupported"
+	// MsgIgnoringLaunchCache user facing error message
+	MsgIgnoringLaunchCache = "Ignoring -launch-cache, only intended for use with -daemon"
 )
 
 func ResolveInputs(phase LifecyclePhase, i *LifecycleInputs, logger log.Logger) error {
@@ -178,7 +183,7 @@ func fillRunImageFromStackTOMLIfNeeded(i *LifecycleInputs, logger log.Logger) er
 	}
 	i.RunImageRef, err = BestRunImageMirrorFor(targetRegistry, stackMD.RunImage, i.AccessChecker())
 	if err != nil {
-		return errors.New(ErrRunImageRequiredWhenNoStackMD)
+		return err
 	}
 	return nil
 }
diff --git a/platform/run_image.go b/platform/run_image.go
index c0273e18..12cc283b 100644
--- a/platform/run_image.go
+++ b/platform/run_image.go
@@ -25,7 +25,7 @@ const (
 func BestRunImageMirrorFor(targetRegistry string, runImageMD files.RunImageForExport, checkReadAccess CheckReadAccess) (string, error) {
 	var runImageMirrors []string
 	if runImageMD.Image == "" {
-		return "", errors.New("missing run image metadata")
+		return "", errors.New("missing run image metadata (-run-image)")
 	}
 	runImageMirrors = append(runImageMirrors, runImageMD.Image)
 	runImageMirrors = append(runImageMirrors, runImageMD.Mirrors...)


Test patch:
diff --git a/.github/workflows/check-latest-release.yml b/.github/workflows/check-latest-release.yml
index d0e6766b..5bf1ea93 100644
--- a/.github/workflows/check-latest-release.yml
+++ b/.github/workflows/check-latest-release.yml
@@ -9,6 +9,8 @@ jobs:
   check-release:
     runs-on:
       - ubuntu-latest
+    permissions:
+      id-token: write
     steps:
       - uses: actions/checkout@v4
       - uses: actions/setup-go@v4
diff --git a/log/timelog_test.go b/log/timelog_test.go
index fbcedf92..29a99601 100644
--- a/log/timelog_test.go
+++ b/log/timelog_test.go
@@ -24,28 +24,28 @@ func (m mockLog) incr(key string) {
 	}
 }
 
-func (m mockLog) Debug(msg string) {
+func (m mockLog) Debug(_ string) {
 	m.incr("Debug")
 }
-func (m mockLog) Debugf(fmt string, v ...interface{}) {
+func (m mockLog) Debugf(_ string, _ ...interface{}) {
 	m.incr("Debug")
 }
-func (m mockLog) Info(msg string) {
+func (m mockLog) Info(_ string) {
 	m.incr("Info")
 }
-func (m mockLog) Infof(fmt string, v ...interface{}) {
+func (m mockLog) Infof(_ string, _ ...interface{}) {
 	m.incr("Info")
 }
-func (m mockLog) Warn(msg string) {
+func (m mockLog) Warn(_ string) {
 	m.incr("Warn")
 }
-func (m mockLog) Warnf(fmt string, v ...interface{}) {
+func (m mockLog) Warnf(_ string, _ ...interface{}) {
 	m.incr("Warn")
 }
-func (m mockLog) Error(msg string) {
+func (m mockLog) Error(_ string) {
 	m.incr("Error")
 }
-func (m mockLog) Errorf(fmt string, v ...interface{}) {
+func (m mockLog) Errorf(_ string, _ ...interface{}) {
 	m.incr("Error")
 }
 
@@ -64,20 +64,20 @@ func testTimeLog(t *testing.T, when spec.G, it spec.S) {
 
 			c1.Log = logger
 			c1.RecordStart()
-			h.AssertEq(t, logger.callCount["Info"], 1)
+			h.AssertEq(t, logger.callCount["Debug"], 1)
 			h.AssertEq(t, c1.StartTime == nullTime, false)
 			h.AssertEq(t, c1.EndTime, nullTime)
 
 			c1.RecordEnd()
-			h.AssertEq(t, logger.callCount["Info"], 2)
+			h.AssertEq(t, logger.callCount["Debug"], 2)
 			h.AssertEq(t, c1.EndTime == nullTime, false)
 		})
 		it("the convenience functions call the logger", func() {
 			logger := mockLog{callCount: map[string]int{}}
 			endfunc := log.NewMeasurement("value", logger)
-			h.AssertEq(t, logger.callCount["Info"], 1)
+			h.AssertEq(t, logger.callCount["Debug"], 1)
 			endfunc()
-			h.AssertEq(t, logger.callCount["Info"], 2)
+			h.AssertEq(t, logger.callCount["Debug"], 2)
 		})
 	})
 }
diff --git a/phase/cache_test.go b/phase/cache_test.go
index b97ccf8a..03971e7c 100644
--- a/phase/cache_test.go
+++ b/phase/cache_test.go
@@ -269,19 +269,19 @@ func testCache(t *testing.T, when spec.G, it spec.S) {
 					AnyTimes()
 				layersDir = filepath.Join("testdata", "cacher", "invalid-layers")
 				h.AssertNil(t, exporter.Cache(layersDir, testCache))
-				h.AssertEq(t, len(logHandler.Entries), 5)
+				h.AssertEq(t, len(logHandler.Entries), 3)
 			})
 
 			it("warns when there is a cache=true layer without contents", func() {
-				h.AssertStringContains(t, logHandler.Entries[1].Message, "Failed to cache layer 'buildpack.id:cache-true-no-contents' because it has no contents")
+				h.AssertStringContains(t, logHandler.Entries[0].Message, "Failed to cache layer 'buildpack.id:cache-true-no-contents' because it has no contents")
 			})
 
 			it("warns when there is an error adding a layer", func() {
-				h.AssertStringContains(t, logHandler.Entries[2].Message, "Failed to cache layer 'buildpack.id:layer-1': creating layer 'buildpack.id:layer-1': test error")
+				h.AssertStringContains(t, logHandler.Entries[1].Message, "Failed to cache layer 'buildpack.id:layer-1': creating layer 'buildpack.id:layer-1': test error")
 			})
 
 			it("continues caching valid layers", func() {
-				h.AssertStringContains(t, logHandler.Entries[3].Message, "Adding cache layer 'buildpack.id:layer-2'")
+				h.AssertStringContains(t, logHandler.Entries[2].Message, "Adding cache layer 'buildpack.id:layer-2'")
 				assertCacheHasLayer(t, testCache, "buildpack.id:layer-2")
 			})
 		})
diff --git a/platform/resolve_analyze_inputs_test.go b/platform/resolve_analyze_inputs_test.go
index 4f4b560f..5e738cf4 100644
--- a/platform/resolve_analyze_inputs_test.go
+++ b/platform/resolve_analyze_inputs_test.go
@@ -111,7 +111,7 @@ func testResolveAnalyzeInputs(platformAPI string) func(t *testing.T, when spec.G
 								inputs.StackPath = "not-exist-stack.toml"
 								err := platform.ResolveInputs(platform.Analyze, inputs, logger)
 								h.AssertNotNil(t, err)
-								expected := "-run-image is required when there is no stack metadata available"
+								expected := "missing run image metadata (-run-image)"
 								h.AssertStringContains(t, err.Error(), expected)
 							})
 						})
diff --git a/platform/resolve_create_inputs_test.go b/platform/resolve_create_inputs_test.go
index 51d8fbc0..105afcad 100644
--- a/platform/resolve_create_inputs_test.go
+++ b/platform/resolve_create_inputs_test.go
@@ -110,7 +110,7 @@ func testResolveCreateInputs(platformAPI string) func(t *testing.T, when spec.G,
 								inputs.StackPath = "not-exist-stack.toml"
 								err := platform.ResolveInputs(platform.Create, inputs, logger)
 								h.AssertNotNil(t, err)
-								expected := "-run-image is required when there is no stack metadata available"
+								expected := "missing run image metadata (-run-image)"
 								h.AssertStringContains(t, err.Error(), expected)
 							})
 						})
diff --git a/platform/run_image_test.go b/platform/run_image_test.go
index 0025ddfb..bc7a6b83 100644
--- a/platform/run_image_test.go
+++ b/platform/run_image_test.go
@@ -235,5 +235,18 @@ func testRunImage(t *testing.T, when spec.G, it spec.S) {
 				h.AssertEq(t, name, "gcr.io/myorg/myrepo")
 			})
 		})
+
+		when("there is no read access", func() {
+			it("fails", func() {
+				noReadAccess := func(_ string, _ authn.Keychain) (bool, error) {
+					return false, nil
+				}
+
+				_, err := platform.BestRunImageMirrorFor("gcr.io", stackMD.RunImage, noReadAccess)
+				h.AssertNotNil(t, err)
+				expected := "failed to find accessible run image"
+				h.AssertStringContains(t, err.Error(), expected)
+			})
+		})
 	})
 }