Repository: buildpacks/lifecycle
Pull request #1142 (2023-07-05 12:16:13)

Add support for s390x
This is the PR to enable buildpacks on the s390x platform.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/buildpacks/lifecycle/pull/1142?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=buildpacks) Report
> Merging [#1142](https://app.codecov.io/gh/buildpacks/lifecycle/pull/1142?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=buildpacks) (e126638) into [main](https://app.codecov.io/gh/buildpacks/lifecycle/commit/9c455eb4ddf745d2a432dc187a00326d7a413a28?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=buildpacks) (9c455eb) will **increase** coverage by `6.94%`.
> The diff coverage is `n/a`.

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/buildpacks/lifecycle/pull/1142/graphs/tree.svg?width=650&height=150&src=pr&token=Zmm2KHcGn9&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=buildpacks)](https://app.codecov.io/gh/buildpacks/lifecycle/pull/1142?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=buildpacks)

```diff
@@            Coverage Diff             @@
##             main    #1142      +/-   ##
==========================================
+ Coverage   58.76%   65.69%   +6.94%     
==========================================
  Files          88       91       +3     
  Lines        6811     7027     +216     
==========================================
+ Hits         4002     4616     +614     
+ Misses       2448     1987     -461     
- Partials      361      424      +63     
```

| Flag | Coverage Δ | |
|---|---|---|
| os_linux | `65.69% <ø> (?)` | |
| os_windows | `?` | |
| unit | `65.69% <ø> (?)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=buildpacks#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/buildpacks/lifecycle/pull/1142?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=buildpacks) Report
> Merging [#1142](https://app.codecov.io/gh/buildpacks/lifecycle/pull/1142?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=buildpacks) (4615dc1) into [main](https://app.codecov.io/gh/buildpacks/lifecycle/commit/f8b3419877ceab27f46af0621ec6d9d80650df44?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=buildpacks) (f8b3419) will **decrease** coverage by `0.50%`.
> The diff coverage is `n/a`.

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/buildpacks/lifecycle/pull/1142/graphs/tree.svg?width=650&height=150&src=pr&token=Zmm2KHcGn9&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=buildpacks)](https://app.codecov.io/gh/buildpacks/lifecycle/pull/1142?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=buildpacks)

```diff
@@            Coverage Diff             @@
##             main    #1142      +/-   ##
==========================================
- Coverage   65.92%   65.42%   -0.50%     
==========================================
  Files          97       93       -4     
  Lines        7132     7086      -46     
==========================================
- Hits         4701     4635      -66     
- Misses       2011     2027      +16     
- Partials      420      424       +4     
```

| Flag | Coverage Δ | |
|---|---|---|
| os_linux | `65.42% <ø> (ø)` | |
| os_windows | `?` | |
| unit | `65.42% <ø> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=buildpacks#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>
natalieparellano: Fixes https://github.com/buildpacks/lifecycle/issues/811
jabrown85: This looks good to me @natalieparellano 

Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index da15035d..85921034 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -160,6 +160,14 @@ jobs:
           name: lifecycle-linux-arm64-sha256
           path: out/lifecycle-v*+linux.arm64.tgz.sha256
       - uses: actions/upload-artifact@v3
+        with:
+          name: lifecycle-linux-s390x
+          path: out/lifecycle-v*+linux.s390x.tgz
+      - uses: actions/upload-artifact@v2
+        with:
+          name: lifecycle-linux-s390x-sha256
+          path: out/lifecycle-v*+linux.s390x.tgz.sha256
+      - uses: actions/upload-artifact@v2
         with:
           name: lifecycle-windows-x86-64
           path: out/lifecycle-v*+windows.x86-64.tgz
@@ -206,12 +214,16 @@ jobs:
           LINUX_ARM64_SHA=$(go run ./tools/image/main.go -lifecyclePath ./out/lifecycle-v*+linux.arm64.tgz -tag buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG}-linux-arm64 -arch arm64 | awk '{print $NF}')
           echo "LINUX_ARM64_SHA: $LINUX_ARM64_SHA"
 
+          LINUX_S390X_SHA=$(go run ./tools/image/main.go -lifecyclePath ./out/lifecycle-v*+linux.s390x.tgz -tag buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG}-linux-s390x -arch s390x | awk '{print $NF}')
+          echo "LINUX_S390X_SHA: $LINUX_S390X_SHA"
+
           WINDOWS_AMD64_SHA=$(go run ./tools/image/main.go -lifecyclePath ./out/lifecycle-v*+windows.x86-64.tgz -tag buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG}-windows -os windows | awk '{print $NF}')
           echo "WINDOWS_AMD64_SHA: $WINDOWS_AMD64_SHA"
 
           docker manifest create buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG} \
               buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG}-linux-x86-64@${LINUX_AMD64_SHA} \
               buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG}-linux-arm64@${LINUX_ARM64_SHA} \
+              buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG}-linux-s390x@${LINUX_S390X_SHA} \
               buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG}-windows@${WINDOWS_AMD64_SHA}
 
           MANIFEST_SHA=$(docker manifest push buildpacksio/lifecycle:${LIFECYCLE_IMAGE_TAG})
diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml
index 7406533c..858a50af 100644
--- a/.github/workflows/draft-release.yml
+++ b/.github/workflows/draft-release.yml
@@ -24,7 +24,7 @@ jobs:
             exit 1
           fi
           echo "LIFECYCLE_VERSION=$version" >> $GITHUB_ENV
-      - name: Determine download urls for linux-x86-64, linux-arm64, and windows
+      - name: Determine download urls for linux-x86-64, linux-arm64, linux-s390x, and windows
         id: artifact-urls
         # FIXME: this script should be updated to work with actions/github-script@v6
         uses: actions/github-script@v3
@@ -82,8 +82,8 @@ jobs:
                 if (urlList.length === 0) {
                   throw "no artifacts found"
                 }
-                if (urlList.length != 8) {
-                  throw "there should be exactly 8 artifacts"
+                if (urlList.length != 10) {
+                  throw "there should be exactly 10 artifacts"
                 }
                 return urlList.join(",")
               })
diff --git a/.github/workflows/post-release.yml b/.github/workflows/post-release.yml
index 442f06a1..20136abf 100644
--- a/.github/workflows/post-release.yml
+++ b/.github/workflows/post-release.yml
@@ -54,11 +54,13 @@ jobs:
 
           crane tag buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}-linux-x86-64@${{ env.LINUX_AMD64_SHA }} ${{ env.LIFECYCLE_VERSION }}-linux-x86-64
           crane tag buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}-linux-arm64@${{ env.LINUX_ARM64_SHA }} ${{ env.LIFECYCLE_VERSION }}-linux-arm64
+          crane tag buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}-linux-s390x@${{ env.LINUX_S390X_SHA }} ${{ env.LIFECYCLE_VERSION }}-linux-s390x
           crane tag buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}-windows@${{ env.WINDOWS_AMD64_SHA }} ${{ env.LIFECYCLE_VERSION }}-windows
 
           docker manifest create buildpacksio/lifecycle:${{ env.LIFECYCLE_VERSION }} \
             buildpacksio/lifecycle:${{ env.LIFECYCLE_VERSION }}-linux-x86-64@${{ env.LINUX_AMD64_SHA }} \
             buildpacksio/lifecycle:${{ env.LIFECYCLE_VERSION }}-linux-arm64@${{ env.LINUX_ARM64_SHA }} \
+            buildpacksio/lifecycle:${{ env.LIFECYCLE_VERSION }}-linux-s390x@${{ env.LINUX_S390X_SHA }} \
             buildpacksio/lifecycle:${{ env.LIFECYCLE_VERSION }}-windows@${{ env.WINDOWS_AMD64_SHA }}
 
           MANIFEST_SHA=$(docker manifest push buildpacksio/lifecycle:${{ env.LIFECYCLE_VERSION }})
@@ -89,11 +91,13 @@ jobs:
 
           crane tag buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}-linux-x86-64@${{ env.LINUX_AMD64_SHA }} latest-linux-x86-64
           crane tag buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}-linux-arm64@${{ env.LINUX_ARM64_SHA }} latest-linux-arm64
+          crane tag buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}-linux-s390x@${{ env.LINUX_S390X_SHA }} latest-linux-s390x
           crane tag buildpacksio/lifecycle:${{ env.LIFECYCLE_IMAGE_TAG }}-windows@${{ env.WINDOWS_AMD64_SHA }} latest-windows
 
           docker manifest create buildpacksio/lifecycle:latest \
             buildpacksio/lifecycle:latest-linux-x86-64@${{ env.LINUX_AMD64_SHA }} \
             buildpacksio/lifecycle:latest-linux-arm64@${{ env.LINUX_ARM64_SHA }} \
+            buildpacksio/lifecycle:latest-linux-s390x@${{ env.LINUX_S390X_SHA }} \
             buildpacksio/lifecycle:latest-windows@${{ env.WINDOWS_AMD64_SHA }}
 
           MANIFEST_SHA=$(docker manifest push buildpacksio/lifecycle:latest)
diff --git a/Makefile b/Makefile
index 01bf030e..8550d5f3 100644
--- a/Makefile
+++ b/Makefile
@@ -39,11 +39,12 @@ GOFILES := $(shell $(GOCMD) run tools$/lister$/main.go)
 
 all: test build package
 
-build: build-linux-amd64 build-linux-arm64 build-windows-amd64
+build: build-linux-amd64 build-linux-arm64 build-windows-amd64 build-linux-s390x
 
 build-linux-amd64: build-linux-amd64-lifecycle build-linux-amd64-symlinks build-linux-amd64-launcher
 build-linux-arm64: build-linux-arm64-lifecycle build-linux-arm64-symlinks build-linux-arm64-launcher
 build-windows-amd64: build-windows-amd64-lifecycle build-windows-amd64-symlinks build-windows-amd64-launcher
+build-linux-s390x: build-linux-s390x-lifecycle build-linux-s390x-symlinks build-linux-s390x-launcher
 
 build-image-linux-amd64: build-linux-amd64 package-linux-amd64
 build-image-linux-amd64: ARCHIVE_PATH=$(BUILD_DIR)/lifecycle-v$(LIFECYCLE_VERSION)+linux.x86-64.tgz
@@ -60,10 +61,17 @@ build-image-windows-amd64: ARCHIVE_PATH=$(BUILD_DIR)/lifecycle-v$(LIFECYCLE_VERS
 build-image-windows-amd64:
 	$(GOCMD) run ./tools/image/main.go -daemon -lifecyclePath $(ARCHIVE_PATH) -os windows -arch amd64 -tag lifecycle:$(LIFECYCLE_IMAGE_TAG)
 
+build-image-linux-s390x: build-linux-s390x package-linux-s390x
+build-image-linux-s390x: ARCHIVE_PATH=$(BUILD_DIR)/lifecycle-v$(LIFECYCLE_VERSION)+linux.s390x.tgz
+build-image-linux-s390x:
+	$(GOCMD) run ./tools/image/main.go -daemon -lifecyclePath $(ARCHIVE_PATH) -os linux -arch s390x -tag lifecycle:$(LIFECYCLE_IMAGE_TAG)
+
 build-linux-amd64-lifecycle: $(BUILD_DIR)/linux-amd64/lifecycle/lifecycle
 
 build-linux-arm64-lifecycle: $(BUILD_DIR)/linux-arm64/lifecycle/lifecycle
 
+build-linux-s390x-lifecycle: $(BUILD_DIR)/linux-s390x/lifecycle/lifecycle
+
 $(BUILD_DIR)/linux-amd64/lifecycle/lifecycle: export GOOS:=linux
 $(BUILD_DIR)/linux-amd64/lifecycle/lifecycle: export GOARCH:=amd64
 $(BUILD_DIR)/linux-amd64/lifecycle/lifecycle: OUT_DIR?=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle
@@ -82,6 +90,15 @@ $(BUILD_DIR)/linux-arm64/lifecycle/lifecycle:
 	mkdir -p $(OUT_DIR)
 	$(GOENV) $(GOBUILD) -o $(OUT_DIR)/lifecycle -a ./cmd/lifecycle
 
+$(BUILD_DIR)/linux-s390x/lifecycle/lifecycle: export GOOS:=linux
+$(BUILD_DIR)/linux-s390x/lifecycle/lifecycle: export GOARCH:=s390x
+$(BUILD_DIR)/linux-s390x/lifecycle/lifecycle: OUT_DIR?=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle
+$(BUILD_DIR)/linux-s390x/lifecycle/lifecycle: $(GOFILES)
+$(BUILD_DIR)/linux-s390x/lifecycle/lifecycle:
+	@echo "> Building lifecycle/lifecycle for $(GOOS)/$(GOARCH)..."
+	mkdir -p $(OUT_DIR)
+	$(GOENV) $(GOBUILD) -o $(OUT_DIR)/lifecycle -a ./cmd/lifecycle
+
 build-linux-amd64-launcher: $(BUILD_DIR)/linux-amd64/lifecycle/launcher
 
 $(BUILD_DIR)/linux-amd64/lifecycle/launcher: export GOOS:=linux
@@ -106,6 +123,18 @@ $(BUILD_DIR)/linux-arm64/lifecycle/launcher:
 	$(GOENV) $(GOBUILD) -o $(OUT_DIR)/launcher -a ./cmd/launcher
 	test $$(du -m $(OUT_DIR)/launcher|cut -f 1) -le 3
 
+build-linux-s390x-launcher: $(BUILD_DIR)/linux-s390x/lifecycle/launcher
+
+$(BUILD_DIR)/linux-s390x/lifecycle/launcher: export GOOS:=linux
+$(BUILD_DIR)/linux-s390x/lifecycle/launcher: export GOARCH:=s390x
+$(BUILD_DIR)/linux-s390x/lifecycle/launcher: OUT_DIR?=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle
+$(BUILD_DIR)/linux-s390x/lifecycle/launcher: $(GOFILES)
+$(BUILD_DIR)/linux-s390x/lifecycle/launcher:
+	@echo "> Building lifecycle/launcher for $(GOOS)/$(GOARCH)..."
+	mkdir -p $(OUT_DIR)
+	$(GOENV) $(GOBUILD) -o $(OUT_DIR)/launcher -a ./cmd/launcher
+	test $$(du -m $(OUT_DIR)/launcher|cut -f 1) -le 3
+
 build-linux-amd64-symlinks: export GOOS:=linux
 build-linux-amd64-symlinks: export GOARCH:=amd64
 build-linux-amd64-symlinks: OUT_DIR?=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle
@@ -134,6 +163,20 @@ build-linux-arm64-symlinks:
 	ln -sf lifecycle $(OUT_DIR)/creator
 	ln -sf lifecycle $(OUT_DIR)/extender
 
+build-linux-s390x-symlinks: export GOOS:=linux
+build-linux-s390x-symlinks: export GOARCH:=s390x
+build-linux-s390x-symlinks: OUT_DIR?=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle
+build-linux-s390x-symlinks:
+	@echo "> Creating phase symlinks for $(GOOS)/$(GOARCH)..."
+	ln -sf lifecycle $(OUT_DIR)/detector
+	ln -sf lifecycle $(OUT_DIR)/analyzer
+	ln -sf lifecycle $(OUT_DIR)/restorer
+	ln -sf lifecycle $(OUT_DIR)/builder
+	ln -sf lifecycle $(OUT_DIR)/exporter
+	ln -sf lifecycle $(OUT_DIR)/rebaser
+	ln -sf lifecycle $(OUT_DIR)/creator
+	ln -sf lifecycle $(OUT_DIR)/extender
+
 build-windows-amd64-lifecycle: $(BUILD_DIR)/windows-amd64/lifecycle/lifecycle.exe
 
 $(BUILD_DIR)/windows-amd64/lifecycle/lifecycle.exe: export GOOS:=windows
@@ -211,7 +254,7 @@ build-darwin-amd64-launcher:
 	$(eval OUT_DIR := $(BUILD_DIR)/$(TARGET)/lifecycle)
 	$(call build_launcher)
 
-generate-sbom: run-syft-windows run-syft-linux-amd64 run-syft-linux-arm64
+generate-sbom: run-syft-windows run-syft-linux-amd64 run-syft-linux-arm64 run-syft-linux-s390x
 
 run-syft-windows: install-syft
 run-syft-windows: export GOOS:=windows
@@ -237,6 +280,14 @@ run-syft-linux-arm64:
 	syft $(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/lifecycle -o json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/lifecycle.sbom.syft.json -o spdx-json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/lifecycle.sbom.spdx.json -o cyclonedx-json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/lifecycle.sbom.cdx.json
 	syft $(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/launcher -o json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/launcher.sbom.syft.json -o spdx-json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/launcher.sbom.spdx.json -o cyclonedx-json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/launcher.sbom.cdx.json
 
+run-syft-linux-s390x: install-syft
+run-syft-linux-s390x: export GOOS:=linux
+run-syft-linux-s390x: export GOARCH:=s390x
+run-syft-linux-s390x:
+	@echo "> Running syft..."
+	syft $(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/lifecycle -o json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/lifecycle.sbom.syft.json -o spdx-json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/lifecycle.sbom.spdx.json -o cyclonedx-json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/lifecycle.sbom.cdx.json
+	syft $(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/launcher -o json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/launcher.sbom.syft.json -o spdx-json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/launcher.sbom.spdx.json -o cyclonedx-json=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle/launcher.sbom.cdx.json
+
 install-syft:
 	@echo "> Installing syft..."
 	curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
@@ -298,7 +349,7 @@ clean:
 	@echo "> Cleaning workspace..."
 	rm -rf $(BUILD_DIR)
 
-package:  generate-sbom package-linux-amd64 package-linux-arm64 package-windows-amd64
+package:  generate-sbom package-linux-amd64 package-linux-arm64 package-windows-amd64 package-linux-s390x
 
 package-linux-amd64: GOOS:=linux
 package-linux-amd64: GOARCH:=amd64
@@ -318,6 +369,15 @@ package-linux-arm64:
 	@echo "> Packaging lifecycle for $(GOOS)/$(GOARCH)..."
 	$(GOCMD) run $(PACKAGER) --inputDir $(INPUT_DIR) -archivePath $(ARCHIVE_PATH) -descriptorPath $(LIFECYCLE_DESCRIPTOR_PATH) -version $(LIFECYCLE_VERSION)
 
+package-linux-s390x: GOOS:=linux
+package-linux-s390x: GOARCH:=s390x
+package-linux-s390x: INPUT_DIR:=$(BUILD_DIR)/$(GOOS)-$(GOARCH)/lifecycle
+package-linux-s390x: ARCHIVE_PATH=$(BUILD_DIR)/lifecycle-v$(LIFECYCLE_VERSION)+$(GOOS).s390x.tgz
+package-linux-s390x: PACKAGER=./tools/packager/main.go
+package-linux-s390x:
+	@echo "> Packaging lifecycle for $(GOOS)/$(GOARCH)..."
+	$(GOCMD) run $(PACKAGER) --inputDir $(INPUT_DIR) -archivePath $(ARCHIVE_PATH) -descriptorPath $(LIFECYCLE_DESCRIPTOR_PATH) -version $(LIFECYCLE_VERSION)
+
 package-windows-amd64: GOOS:=windows
 package-windows-amd64: GOARCH:=amd64
 package-windows-amd64: INPUT_DIR:=$(BUILD_DIR)$/$(GOOS)-$(GOARCH)$/lifecycle
diff --git a/cmd/lifecycle/restorer.go b/cmd/lifecycle/restorer.go
index 82dc4d48..be1d2ed2 100644
--- a/cmd/lifecycle/restorer.go
+++ b/cmd/lifecycle/restorer.go
@@ -96,10 +96,16 @@ func (r *restoreCmd) Privileges() error {
 }
 
 func (r *restoreCmd) Exec() error {
-	var (
-		analyzedMD files.Analyzed
-		err        error
-	)
+	group, groupExt, err := phase.Config.ReadGroup(r.GroupPath)
+	if err != nil {
+		return cmd.FailErr(err, "read buildpack group")
+	}
+	if err := verifyBuildpackApis(buildpack.Group{Group: group}); err != nil {
+		return err
+	}
+	groupFile := buildpack.Group{Group: group, GroupExtensions: groupExt}
+
+	var analyzedMD files.Analyzed
 	if analyzedMD, err = files.ReadAnalyzed(r.AnalyzedPath, cmd.DefaultLogger); err == nil {
 		if r.supportsBuildImageExtension() && r.BuildImageRef != "" {
 			cmd.DefaultLogger.Debugf("Pulling builder image metadata for %s...", r.BuildImageRef)
@@ -128,17 +134,17 @@ func (r *restoreCmd) Exec() error {
 			// update analyzed metadata, even if we only needed to pull the image metadata, because
 			// the extender needs a digest reference in analyzed.toml,
 			// and daemon images will only have a daemon image ID
-			if err = updateAnalyzedMD(&analyzedMD, runImage); err != nil {
+			if err = r.updateAnalyzedMD(&analyzedMD, runImage); err != nil {
 				return cmd.FailErr(err, "update analyzed metadata")
 			}
-		} else if r.supportsTargetData() && needsUpdating(analyzedMD.RunImage) {
+		} else if r.needsUpdating(analyzedMD.RunImage, groupFile) {
 			cmd.DefaultLogger.Debugf("Updating run image info in analyzed metadata...")
 			h := image.NewHandler(r.docker, r.keychain, r.LayoutDir, r.UseLayout, r.InsecureRegistries)
 			runImage, err = h.InitImage(runImageName)
 			if err != nil || !runImage.Found() {
-				return cmd.FailErr(err, fmt.Sprintf("pull run image %s", runImageName))
+				return cmd.FailErr(err, fmt.Sprintf("get run image %s", runImageName))
 			}
-			if err = updateAnalyzedMD(&analyzedMD, runImage); err != nil {
+			if err = r.updateAnalyzedMD(&analyzedMD, runImage); err != nil {
 				return cmd.FailErr(err, "update analyzed metadata")
 			}
 		}
@@ -149,30 +155,27 @@ func (r *restoreCmd) Exec() error {
 		cmd.DefaultLogger.Warnf("Not using analyzed data, usable file not found: %s", err)
 	}
 
-	group, err := phase.ReadGroup(r.GroupPath)
-	if err != nil {
-		return cmd.FailErr(err, "read buildpack group")
-	}
-	if err := verifyBuildpackApis(group); err != nil {
-		return err
-	}
-
 	cacheStore, err := initCache(r.CacheImageRef, r.CacheDir, r.keychain, r.PlatformAPI.LessThan("0.13"))
 	if err != nil {
 		return err
 	}
-
-	return r.restore(analyzedMD.LayersMetadata, group, cacheStore)
+	return r.restore(analyzedMD.LayersMetadata, groupFile, cacheStore)
 }
 
-func updateAnalyzedMD(analyzedMD *files.Analyzed, remoteRunImage imgutil.Image) error {
-	digestRef, err := remoteRunImage.Identifier()
+func (r *restoreCmd) updateAnalyzedMD(analyzedMD *files.Analyzed, runImage imgutil.Image) error {
+	if r.PlatformAPI.LessThan("0.10") {
+		return nil
+	}
+	digestRef, err := runImage.Identifier()
 	if err != nil {
 		return cmd.FailErr(err, "get digest reference for run image")
 	}
-	targetData, err := platform.GetTargetMetadata(remoteRunImage)
-	if err != nil {
-		return cmd.FailErr(err, "read target data from run image")
+	var targetData *files.TargetMetadata
+	if r.PlatformAPI.AtLeast("0.12") {
+		targetData, err = platform.GetTargetMetadata(runImage)
+		if err != nil {
+			return cmd.FailErr(err, "read target data from run image")
+		}
 	}
 	cmd.DefaultLogger.Debugf("Run image info in analyzed metadata was: ")
 	cmd.DefaultLogger.Debugf(encoding.ToJSONMaybe(analyzedMD.RunImage))
@@ -191,12 +194,18 @@ func needsPulling(runImage *files.RunImage) bool {
 	return runImage.Extend
 }
 
-func needsUpdating(runImage *files.RunImage) bool {
+func (r *restoreCmd) needsUpdating(runImage *files.RunImage, group buildpack.Group) bool {
+	if r.PlatformAPI.LessThan("0.10") {
+		return false
+	}
+	if !group.HasExtensions() {
+		return false
+	}
 	if runImage == nil {
 		// sanity check to prevent panic, should be unreachable
 		return false
 	}
-	if runImage.Reference != "" && isPopulated(runImage.TargetMetadata) {
+	if isPopulated(runImage.TargetMetadata) {
 		return false
 	}
 	return true
diff --git a/phase/generator.go b/phase/generator.go
index a5f35d53..1b26d81d 100644
--- a/phase/generator.go
+++ b/phase/generator.go
@@ -116,9 +116,10 @@ func (g *Generator) Generate() (GenerateResult, error) {
 			g.Logger.Warnf("new runtime base image '%s' not found in run metadata", generatedRunImageRef)
 		}
 		g.Logger.Debugf("Updating analyzed metadata with new run image '%s'", generatedRunImageRef)
-		finalAnalyzedMD.RunImage = &files.RunImage{ // reference and target data are cleared
-			Extend: extend,
-			Image:  generatedRunImageRef,
+		finalAnalyzedMD.RunImage = &files.RunImage{ // target data is cleared
+			Extend:    extend,
+			Reference: generatedRunImageRef,
+			Image:     generatedRunImageRef,
 		}
 	}
 	if extend {


Test patch:
diff --git a/.github/workflows/test-s390x.yml b/.github/workflows/test-s390x.yml
new file mode 100644
index 00000000..6db8f4fa
--- /dev/null
+++ b/.github/workflows/test-s390x.yml
@@ -0,0 +1,87 @@
+name: test-s390x
+on:
+  push:
+    branches:
+      - main
+      - 'release/**'
+  pull_request:
+    branches:
+      - main
+      - 'release/**'
+
+jobs:
+  test-linux-s390x:
+    if: (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release*')
+    runs-on: ubuntu-latest
+    env:
+      ZVSI_FP_NAME: bp-floating-ci-${{ github.run_id }} 
+      ZVSI_INSTANCE_NAME: bp-zvsi-ci-${{ github.run_id }} 
+      ZVSI_ZONE_NAME: ca-tor-1
+      ZVSI_PROFILE_NAME: bz2-4x16
+
+    strategy:
+      fail-fast: false
+    steps:
+      - uses: actions/checkout@v3
+      - name: install ibmcli and setup ibm login 
+        run: |
+          curl -fsSL https://clis.cloud.ibm.com/install/linux | sh
+          ibmcloud login -q --apikey ${{ secrets.IBMCLOUD_API_KEY }} -r ca-tor
+          ibmcloud plugin install vpc-infrastructure 
+      - name: Creation of ZVSI
+        id: ZVSI
+        run: |
+          #creation of zvsi
+          ibmcloud is instance-create $ZVSI_INSTANCE_NAME ${{ secrets.ZVSI_VPC }} $ZVSI_ZONE_NAME $ZVSI_PROFILE_NAME ${{ secrets.ZVSI_SUBNET }} --image ${{ secrets.ZVSI_IMAGE }} --keys ${{ secrets.ZVSI_KEY }} --resource-group-id ${{ secrets.ZVSI_RG_ID }} --sgs ${{ secrets.ZVSI_SG }}
+          #Reserving a floating ip to the ZVSI
+          ibmcloud is floating-ip-reserve $ZVSI_FP_NAME --zone $ZVSI_ZONE_NAME --resource-group-id ${{ secrets.ZVSI_RG_ID }} --in $ZVSI_INSTANCE_NAME
+          #Bouding the Floating ip to the ZVSI
+          ibmcloud is floating-ip-update $ZVSI_FP_NAME --nic primary --in $ZVSI_INSTANCE_NAME          
+          sleep 60
+          #Saving the Floating IP to login ZVSI
+          ZVSI_HOST=$(ibmcloud is floating-ip $ZVSI_FP_NAME | awk '/Address/{print $2}')
+          echo $ZVSI_HOST
+          echo "IP=${ZVSI_HOST}" >> $GITHUB_OUTPUT
+      - name: Status of ZVSI
+        run: |
+          check=$(ibmcloud is ins| awk '/'$ZVSI_INSTANCE_NAME'/{print $3}')
+          while [[ $check != "running" ]]
+          do
+            check=$(ibmcloud is ins | awk '/'$ZVSI_INSTANCE_NAME'/{print $3}')
+            if [[ $check == 'failed' ]]
+            then
+              echo "Failed to run the ZVSI"
+              break
+              fi
+          done
+      - name: Install dependencies and run all tests on s390x ZVSI
+        uses: appleboy/ssh-action@v0.1.10
+        env:
+          GH_REPOSITORY: ${{ github.server_url }}/${{ github.repository }}
+          GH_REF: ${{ github.ref }}
+        with: 
+          host: ${{ steps.ZVSI.outputs.IP }}
+          username: ${{ secrets.ZVSI_SSH_USER }}
+          key: ${{ secrets.ZVSI_PR_KEY }}
+          envs: GH_REPOSITORY,GH_REF
+          command_timeout: 100m
+          script: |
+            apt-get update -y
+            apt-get install -y wget curl git make gcc jq docker.io
+            wget https://go.dev/dl/go1.20.6.linux-s390x.tar.gz
+            rm -rf /usr/local/go && tar -C /usr/local -xzf go1.20.6.linux-s390x.tar.gz
+            export PATH=$PATH:/usr/local/go/bin
+            git clone ${GH_REPOSITORY} lifecycle
+            cd lifecycle && git checkout ${GH_REF}
+            go env
+            export PATH=$PATH:~/go/bin
+            make format || true
+            make test
+      - name: Cleanup ZVSI
+        if: ${{ steps.ZVSI.conclusion == 'success' && always() }}
+        run: |
+          #Delete the created ZVSI
+          ibmcloud is instance-delete $ZVSI_INSTANCE_NAME --force
+          sleep 20
+          #Release the created FP
+          ibmcloud is floating-ip-release $ZVSI_FP_NAME --force
\ No newline at end of file
diff --git a/acceptance/restorer_test.go b/acceptance/restorer_test.go
index 60e06eb2..52eb22c4 100644
--- a/acceptance/restorer_test.go
+++ b/acceptance/restorer_test.go
@@ -249,6 +249,7 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
 
 		when("target data", func() {
 			it("updates run image reference in analyzed.toml to include digest and target data on newer platforms", func() {
+				h.SkipIf(t, api.MustParse(platformAPI).LessThan("0.10"), "")
 				h.DockerRunAndCopy(t,
 					containerName,
 					copyDir,
@@ -278,10 +279,14 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
 					h.AssertNil(t, err)
 					h.AssertEq(t, len(fis), 1) // .gitkeep
 				} else {
-					t.Log("doesn't update analyzed.toml")
+					t.Log("updates run image reference in analyzed.toml to include digest only")
 					analyzedMD, err := phase.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "some-extend-false-analyzed.toml"), cmd.DefaultLogger)
 					h.AssertNil(t, err)
+					h.AssertStringContains(t, analyzedMD.RunImage.Reference, restoreRegFixtures.ReadOnlyRunImage+"@sha256:")
+					h.AssertEq(t, analyzedMD.RunImage.Image, restoreRegFixtures.ReadOnlyRunImage)
 					h.AssertNil(t, analyzedMD.RunImage.TargetMetadata)
+					t.Log("does not return the digest for an empty image")
+					h.AssertStringDoesNotContain(t, analyzedMD.RunImage.Reference, restoreRegFixtures.ReadOnlyRunImage+"@sha256:"+emptyImageSHA)
 				}
 			})
 
diff --git a/acceptance/testdata/restorer/container/layers/group.toml b/acceptance/testdata/restorer/container/layers/group.toml
index d22f3623..f45252f7 100644
--- a/acceptance/testdata/restorer/container/layers/group.toml
+++ b/acceptance/testdata/restorer/container/layers/group.toml
@@ -7,3 +7,8 @@
   id = "cacher_buildpack"
   version = "cacher_v1"
   api = "0.10"
+
+[[group-extensions]]
+    id = "some-extension-id"
+    version = "v1"
+    api = "0.10"
diff --git a/acceptance/testdata/restorer/container/layers/some-extend-false-analyzed.toml.placeholder b/acceptance/testdata/restorer/container/layers/some-extend-false-analyzed.toml.placeholder
index 9569caab..6cd50e33 100644
--- a/acceptance/testdata/restorer/container/layers/some-extend-false-analyzed.toml.placeholder
+++ b/acceptance/testdata/restorer/container/layers/some-extend-false-analyzed.toml.placeholder
@@ -1,3 +1,3 @@
 [run-image]
-  reference = ""
+  reference = "some-reference-without-digest"
   image = "REPLACE"
diff --git a/phase/generator_test.go b/phase/generator_test.go
index d6b91abe..7eb7f57f 100644
--- a/phase/generator_test.go
+++ b/phase/generator_test.go
@@ -254,6 +254,7 @@ func testGenerator(t *testing.T, when spec.G, it spec.S) {
 			_, err := generator.Generate()
 			h.AssertNil(t, err)
 		})
+
 		it("passes through CNB_TARGET environment variables", func() {
 			generator.AnalyzedMD = files.Analyzed{
 				RunImage: &files.RunImage{
@@ -295,6 +296,7 @@ func testGenerator(t *testing.T, when spec.G, it spec.S) {
 			_, err := generator.Generate()
 			h.AssertNil(t, err)
 		})
+
 		it("copies Dockerfiles and extend-config.toml files to the correct locations", func() {
 			// mock generate for extension A
 			dirStore.EXPECT().LookupExt("A", "v1").Return(&extA, nil)
@@ -405,15 +407,16 @@ func testGenerator(t *testing.T, when spec.G, it spec.S) {
 			})
 
 			type testCase struct {
-				before                 func()
-				descCondition          string
-				descResult             string
-				aDockerfiles           []buildpack.DockerfileInfo
-				bDockerfiles           []buildpack.DockerfileInfo
-				expectedRunImageImage  string
-				expectedRunImageExtend bool
-				expectedErr            string
-				assertAfter            func()
+				before                    func()
+				descCondition             string
+				descResult                string
+				aDockerfiles              []buildpack.DockerfileInfo
+				bDockerfiles              []buildpack.DockerfileInfo
+				expectedRunImageImage     string
+				expectedRunImageReference string
+				expectedRunImageExtend    bool
+				expectedErr               string
+				assertAfter               func()
 			}
 			for _, tc := range []testCase{
 				{
@@ -433,8 +436,9 @@ func testGenerator(t *testing.T, when spec.G, it spec.S) {
 						WithBase:    "",
 						Extend:      true,
 					}},
-					expectedRunImageImage:  "some-existing-run-image",
-					expectedRunImageExtend: true,
+					expectedRunImageImage:     "some-existing-run-image",
+					expectedRunImageReference: "some-existing-run-image@sha256:s0m3d1g3st",
+					expectedRunImageExtend:    true,
 				},
 				{
 					descCondition: "a run.Dockerfile declares a new base image and run.Dockerfiles follow",
@@ -457,8 +461,9 @@ func testGenerator(t *testing.T, when spec.G, it spec.S) {
 							Extend:      true,
 						},
 					},
-					expectedRunImageImage:  "some-new-run-image",
-					expectedRunImageExtend: true,
+					expectedRunImageImage:     "some-new-run-image",
+					expectedRunImageReference: "some-new-run-image",
+					expectedRunImageExtend:    true,
 				},
 				{
 					descCondition: "a run.Dockerfile declares a new base image (only) and no run.Dockerfiles follow",
@@ -481,8 +486,9 @@ func testGenerator(t *testing.T, when spec.G, it spec.S) {
 							Extend:      false,
 						},
 					},
-					expectedRunImageImage:  "some-other-base-image",
-					expectedRunImageExtend: false,
+					expectedRunImageImage:     "some-other-base-image",
+					expectedRunImageReference: "some-other-base-image",
+					expectedRunImageExtend:    false,
 					assertAfter: func() {
 						t.Log("copies Dockerfiles to the correct locations")
 						t.Log("renames earlier run.Dockerfiles to Dockerfile.ignore in the output directory")
@@ -504,9 +510,10 @@ func testGenerator(t *testing.T, when spec.G, it spec.S) {
 							Extend:      true,
 						},
 					},
-					bDockerfiles:           []buildpack.DockerfileInfo{},
-					expectedRunImageImage:  "some-new-run-image",
-					expectedRunImageExtend: true,
+					bDockerfiles:              []buildpack.DockerfileInfo{},
+					expectedRunImageImage:     "some-new-run-image",
+					expectedRunImageReference: "some-new-run-image",
+					expectedRunImageExtend:    true,
 				},
 				{
 					before: func() {
@@ -527,9 +534,10 @@ func testGenerator(t *testing.T, when spec.G, it spec.S) {
 							Extend:      false,
 						},
 					},
-					bDockerfiles:           []buildpack.DockerfileInfo{},
-					expectedRunImageImage:  "some-new-run-image",
-					expectedRunImageExtend: false,
+					bDockerfiles:              []buildpack.DockerfileInfo{},
+					expectedRunImageImage:     "some-new-run-image",
+					expectedRunImageReference: "some-new-run-image",
+					expectedRunImageExtend:    false,
 				},
 				{
 					before: func() {
@@ -550,8 +558,9 @@ func testGenerator(t *testing.T, when spec.G, it spec.S) {
 							Extend:      false,
 						},
 					},
-					bDockerfiles:          []buildpack.DockerfileInfo{},
-					expectedRunImageImage: "some-other-run-image",
+					bDockerfiles:              []buildpack.DockerfileInfo{},
+					expectedRunImageImage:     "some-other-run-image",
+					expectedRunImageReference: "some-other-run-image",
 					assertAfter: func() {
 						h.AssertLogEntry(t, logHandler, "new runtime base image 'some-other-run-image' not found in run metadata")
 					},
@@ -581,6 +590,7 @@ func testGenerator(t *testing.T, when spec.G, it spec.S) {
 						result, err := generator.Generate()
 						if err == nil {
 							h.AssertEq(t, result.AnalyzedMD.RunImage.Image, tc.expectedRunImageImage)
+							h.AssertEq(t, result.AnalyzedMD.RunImage.Reference, tc.expectedRunImageReference)
 							h.AssertEq(t, result.AnalyzedMD.RunImage.Extend, tc.expectedRunImageExtend)
 						} else {
 							t.Log(err)