Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ jobs:
sbt-disk-cache: "false"
cache-mode: save
purge: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'purge') }}
cache-epoch: "5a3a551ef12b52cb"
cache-epoch: "99966e7ef507c25b"
- name: cache-rehydrate
run: sbt 'Test/compile'
test:
Expand All @@ -104,7 +104,7 @@ jobs:
sbt-disk-cache: "false"
cache-mode: save
purge: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'purge') }}
cache-epoch: "5a3a551ef12b52cb"
cache-epoch: "99966e7ef507c25b"
- name: Pre-pull remote-cache IT images
run: |
set -euo pipefail
Expand Down Expand Up @@ -177,7 +177,7 @@ jobs:
sbt-disk-cache: "false"
cache-mode: restore
purge: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'purge') }}
cache-epoch: "5a3a551ef12b52cb"
cache-epoch: "99966e7ef507c25b"
- name: fmt
run: |
if [ "$ZIPX_VERIFY_CLEAN_FULL" = "true" ]; then
Expand Down Expand Up @@ -210,7 +210,7 @@ jobs:
sbt-disk-cache: "false"
cache-mode: restore
purge: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'purge') }}
cache-epoch: "5a3a551ef12b52cb"
cache-epoch: "99966e7ef507c25b"
- name: workflow-check
run: |
if [ "$ZIPX_VERIFY_CLEAN_FULL" = "true" ]; then
Expand Down Expand Up @@ -243,7 +243,7 @@ jobs:
sbt-disk-cache: "false"
cache-mode: restore
purge: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'purge') }}
cache-epoch: "5a3a551ef12b52cb"
cache-epoch: "99966e7ef507c25b"
- name: advisories
run: |
if [ "$ZIPX_VERIFY_CLEAN_FULL" = "true" ]; then
Expand Down Expand Up @@ -280,7 +280,7 @@ jobs:
sbt-disk-cache: "false"
cache-mode: restore
purge: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'purge') }}
cache-epoch: "5a3a551ef12b52cb"
cache-epoch: "99966e7ef507c25b"
- name: Fetch PR base SHA
run: git fetch --no-tags origin "$ZIPX_MODVER_BASE_SHA"
- name: modver-suggest
Expand Down Expand Up @@ -318,7 +318,7 @@ jobs:
sbt-disk-cache: "false"
cache-mode: restore
purge: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'purge') }}
cache-epoch: "5a3a551ef12b52cb"
cache-epoch: "99966e7ef507c25b"
- name: Fetch PR base SHA
run: git fetch --no-tags origin "$ZIPX_MODVER_BASE_SHA"
- name: modver-check
Expand Down Expand Up @@ -353,7 +353,7 @@ jobs:
sbt-disk-cache: "false"
cache-mode: restore
purge: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'purge') }}
cache-epoch: "5a3a551ef12b52cb"
cache-epoch: "99966e7ef507c25b"
- name: scripted
run: |
if [ "$ZIPX_VERIFY_CLEAN_FULL" = "true" ]; then
Expand Down Expand Up @@ -405,7 +405,7 @@ jobs:
sbt-disk-cache: "false"
cache-mode: restore
purge: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'purge') }}
cache-epoch: "5a3a551ef12b52cb"
cache-epoch: "99966e7ef507c25b"
- name: snapshots
run: sbt 'zipxSnapshotPublish'
snapshots-pr:
Expand Down Expand Up @@ -434,7 +434,7 @@ jobs:
sbt-disk-cache: "false"
cache-mode: restore
purge: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'purge') }}
cache-epoch: "5a3a551ef12b52cb"
cache-epoch: "99966e7ef507c25b"
- name: snapshots-pr
run: sbt 'zipxSnapshotPublish pr'
docs:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/zipx-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
sbt-disk-cache: "false"
cache-mode: restore
purge: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'purge') }}
cache-epoch: "5a3a551ef12b52cb"
cache-epoch: "99966e7ef507c25b"
- name: Tag is on the default branch
if: github.event_name == 'push'
run: |
Expand Down
32 changes: 14 additions & 18 deletions modules/core/src/main/scala/zipx/core/SbtImageLayer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -254,13 +254,7 @@ object SbtImageLayer:
else
val amd = packages.map(pkg => s"${pkg.name}=${pkg.amd}").mkString(" ")
val arm = packages.map(pkg => s"${pkg.name}=${pkg.arm}").mkString(" ")
List(
"""case "$(uname -m)" in""",
s" x86_64) pkgs=${q(amd)} ;;",
s" aarch64) pkgs=${q(arm)} ;;",
""" *) echo "zipx: unsupported architecture $(uname -m)" >&2; exit 1 ;;""",
"esac",
)
List(archCase("pkgs", amd, arm))
run(
choose ++ List(
"set -f",
Expand Down Expand Up @@ -328,17 +322,19 @@ object SbtImageLayer:
/** Lines that set `url`, and the shell word that expands to it. One word when both arches share a URL. */
private def archUrl(amd: String, arm: String): (List[String], String) =
if amd == arm then (Nil, q(amd))
else
(
List(
"""case "$(uname -m)" in""",
s" x86_64) url=${q(amd)} ;;",
s" aarch64) url=${q(arm)} ;;",
""" *) echo "zipx: unsupported architecture $(uname -m)" >&2; exit 1 ;;""",
"esac",
),
"\"$url\"",
)
else (List(archCase("url", amd, arm)), "\"$url\"")

/** A `case` on `uname -m` that sets `name`. It is one statement to [[run]]: a `;` after `in` or after `;;` is a sh
* syntax error, so the arms join on a bare continuation.
*/
private def archCase(name: String, amd: String, arm: String): String =
List(
"""case "$(uname -m)" in""",
s" x86_64) $name=${q(amd)} ;;",
s" aarch64) $name=${q(arm)} ;;",
""" *) echo "zipx: unsupported architecture $(uname -m)" >&2; exit 1 ;;""",
"esac",
).mkString(" \\\n ")

private def run(lines: List[String]): String =
"RUN set -eux; \\\n " + lines.mkString("; \\\n ")
Expand Down
34 changes: 34 additions & 0 deletions modules/core/src/test/scala/zipx/core/SbtImageSpec.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package zipx.core

import zipx.workflow.*
import zio.*
import zio.test.*

object SbtImageSpec extends ZIOSpecDefault:
Expand Down Expand Up @@ -61,6 +62,22 @@ object SbtImageSpec extends ZIOSpecDefault:
}
.withEnv("WIDGET_PATH" -> "/usr/local/bin/gadget")

/** Each `RUN` command as the shell receives it: Docker drops every backslash-newline continuation. */
private def runBodies(dockerfile: String): List[String] =
dockerfile
.replace("\\\n", "")
.linesIterator
.collect { case line if line.startsWith("RUN ") => line.stripPrefix("RUN ") }
.toList

/** `sh -n` parses without running. Exit status and diagnostics are data for the assertion. */
private def shellCheck(script: String): Task[(Int, String)] =
ZIO.attemptBlocking {
val process = new ProcessBuilder("sh", "-n", "-c", script).redirectErrorStream(true).start()
val output = String(process.getInputStream.readAllBytes())
(process.waitFor(), output)
}

private def resolve(extra: List[Pin], layers: Seq[SbtImageLayer]): Either[SbtImageError, SbtImagePlan] =
SbtImage.resolve(JdkVersion("21"), imagePin(pin) :: extra, Nil, coursier = false, docker = false, layers)

Expand Down Expand Up @@ -353,6 +370,23 @@ object SbtImageSpec extends ZIOSpecDefault:
text.contains("set -f"),
)
},
test("every RUN parses as sh when layer URLs and apt versions differ by arch") {
val gizmos = SbtImageLayer("gizmos", noLookup).install { (_, arch) =>
val deb = arch match
case CpuArch.Amd64 => "1.0-amd64"
case CpuArch.Arm64 => "1.0-arm64"
List(ImageStep.Apt("libgizmos", deb))
}
val pins = List(toolPin("widgets", "1.2.3"), toolPin("gizmos", "1.0.0"))
val text = resolve(pins, List(widgets(widgetSteps), gizmos)).toOption.flatMap(_.dockerfile).getOrElse("")
val runs = runBodies(text)
ZIO
.foreach(runs)(body => shellCheck(body).map(body -> _))
.map { checked =>
val broken = checked.collect { case (body, (exit, out)) if exit != 0 => s"$out\n$body" }
assertTrue(runs.exists(_.contains("uname -m")), broken.isEmpty) ?? broken.mkString("\n\n")
}
},
test("a download URL does not have to contain the pin text") {
val layer = SbtImageLayer("widgets", noLookup).install { (version, arch) =>
val revision = if version == "1.2.3" then "1148" else "0"
Expand Down
Loading