sbt / sbt-native-packager

sbt Native Packager
https://sbt-native-packager.readthedocs.io/en/stable/
BSD 2-Clause "Simplified" License
1.59k stars 439 forks source link

GraalVMNativeImagePlugin produces broken docker images without native-image in the PATH #1492

Closed pshirshov closed 2 years ago

pshirshov commented 2 years ago

Try to enable GraalVMNativeImagePlugin on a project and set

   graalVMNativeImageGraalVersion := Some("22.0.0.2"),

This would produce a broken docker image:

❯ docker run -ti --rm ghcr.io-graalvm-graalvm-ce-native-image:22.0.0.2
docker: Error response from daemon: OCI runtime create failed: container_linux.go:380: starting container process caused: exec: "native-image": executable file not found in $PATH: unknown.
❯ docker run -ti --rm --entrypoint /bin/bash ghcr.io-graalvm-graalvm-ce-native-image:22.0.0.2
bash-4.4# native-image
bash: native-image: command not found
bash-4.4# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
bash-4.4# find / -name "native-image" -print
/opt/graalvm-ce-java17-22.0.0.2/bin/native-image
/opt/graalvm-ce-java17-22.0.0.2/lib/svm/bin/native-image
bash-4.4# exit
pshirshov commented 2 years ago

And here is a quick monkeypatch:

    GraalVMNativeImage / UniversalPlugin.autoImport.containerBuildImage := Def.taskDyn {
      (Def.task {

        val baseImage     = s"ghcr.io/graalvm/graalvm-ce:ol8-${graalVMNativeImageGraalVersion.value.get}"
        val dockerCommand = (DockerPlugin.autoImport.dockerExecCommand in GraalVMNativeImage).value
        val streams       = Keys.streams.value

        val (baseName, tag) = baseImage.split(":", 2) match {
          case Array(n, t) => (n, t)
          case Array(n)    => (n, "latest")
        }

        val imageName = s"${baseName.replace('/', '-')}-native-image:$tag"
        import sys.process._
        if ((dockerCommand ++ Seq("image", "ls", imageName, "--quiet")).!!.trim.isEmpty) {
          streams.log.info(s"Generating new GraalVM native-image image based on $baseImage: $imageName")

          val dockerContent = Dockerfile(
            Cmd("FROM", baseImage),
            Cmd("WORKDIR", "/opt/graalvm"),
            ExecCmd("RUN", "gu", "install", "native-image"),
            ExecCmd("ENTRYPOINT", "native-image"),
            ExecCmd("RUN", "ln", "-s", s"/opt/graalvm-ce-${graalVMNativeImageGraalVersion.value.get}/bin/native-image", "/usr/local/bin/native-image"),
          ).makeContent

          val command = dockerCommand ++ Seq("build", "-t", imageName, "-")

          val pb: ProcessBuilder = sys.process.Process(command) #< new ByteArrayInputStream(dockerContent.getBytes())
          val ret = pb ! (DockerPlugin: { def publishLocalLogger(log: sbt.Logger): scala.AnyRef with scala.sys.process.ProcessLogger }).publishLocalLogger(streams.log)

          if (ret != 0)
            throw new RuntimeException("Nonzero exit value when generating GraalVM container build image: " + ret)

        } else
          streams.log.info(s"Using existing GraalVM native-image image: $imageName")

        Some(imageName)
      }: Def.Initialize[Task[Option[String]]])
    }.value,
muuki88 commented 2 years ago

Thanks a lot @pshirshov

Could you explain what the issue is and open a PR? 😊👋

pshirshov commented 2 years ago

and open a PR? blushwave

Yep

Could you explain what the issue

Well, in case the explanation I've added is not enough - probably I won't be able to explain better :(

capacman commented 2 years ago

I think issue is related to graalvm image. With prior versions(prior to graalvm 22) gu install process creating a symbolic link(via alternatives i think) under /usr/bin but with version 22 it just install native-image binary under $JAVA_HOME/bin.

capacman commented 2 years ago

I think it is as issue with all recently published graalvm images.i tried with 21.3.1 and failed again

kgston commented 2 years ago

Would be nice to get this fix in soon since there is no manual workaround it affects updated graalvm images on old versions too. My only concern is that it locks the OS type to ol8 in the monkeypatch. But I suppose it could be an additional option i.e. graalVMNativeImageGraalOSVersion.

pshirshov commented 2 years ago

That's not the best possible solution, I guess it would be better to allow the user to override the native-image binary location

kgston commented 2 years ago

You can already override it to use the native-image binary if you like: GraalVMNativeImage / containerBuildImage := Some(s"ghcr.io/graalvm/native-image:ol8-java17-22.0.0.2")

I wrote it that way to keep using the baseline image (not sure how important that is really..). And I noticed that the native-image docker images do not get updated as quickly as the baseline image either.

pshirshov commented 2 years ago

Yes, but that's kinda painful.