salesforce / rules_spring

Bazel rule for building Spring Boot apps as a deployable jar
BSD 3-Clause "New" or "Revised" License
224 stars 49 forks source link

Output jar includes timestamps, not hermetic (want singlejar) #19

Closed joeljeske closed 4 years ago

joeljeske commented 4 years ago

The packaging step uses jar. That creates an archive with time stamps, yielding the artifact not hermetic. Bazel internally uses singlejar to package up jars which sets timestamps to a fixed value.

Have you considered using singlejar for its packaging (or another packaging tool), or somehow create a jar that has fixed timestamps and other varying bytes removed.

plaird commented 4 years ago

Good suggestion. I think singlejar is the right way.

That being said, I remember trying this in the prehistoric days of this rule but it didn't work. My notes are below. In short, the Spring Boot launcher couldn't handle a 0 timestamp on the internal files.

The intent was to circle back on this issue later on (and maybe file a bug against Spring Boot), but we ended up closing it during a backlog cleanup. The reason is for our internal use cases we care a lot more about the inputs being hermetic (see buildstamp docs) so remote cache would work, less about the output being hermetic since nothing ever depends on our springboot output in our build.

But, now that springboot rule is a public repo, we should do the right thing. singlejar is the right way, so this is a good issue.

====

This seems to be unique to Spring Boot 2.x. I suspect there is a bug in the spring boot loader infra, possibly introduced by this change: https://github.com/spring-projects/spring-boot/commit/6081db5c346d239e2a1295e53fc3ceb035ffd605

I think this is aggravated by something not handling TZ correctly. Bazel generates artifacts with timestamps of 0 and the Spring Boot loader assumes Jar epoch is Jan 1 1980. When the GMT offset is applied (e.g. -7 for Colorado) that becomes an invalid date/time for Spring Boot. When Simon generates the same artifact on his computer in Japan TZ, everything works and the timestamp of BOOT-INF/lib artifacts is Jan 1 1980 at 9am.

Full stack trace: Exception in thread "main" java.time.DateTimeException: Invalid value for MonthOfYear (valid values 1 - 12): 0 at java.time.temporal.ValueRange.checkValidValue(ValueRange.java:311) at java.time.temporal.ChronoField.checkValidValue(ChronoField.java:703) at java.time.LocalDate.of(LocalDate.java:267) at java.time.LocalDateTime.of(LocalDateTime.java:336) at org.springframework.boot.loader.jar.CentralDirectoryFileHeader.decodeMsDosFormatDateTime(CentralDirectoryFileHeader.java:130) at org.springframework.boot.loader.jar.CentralDirectoryFileHeader.getTime(CentralDirectoryFileHeader.java:119) at org.springframework.boot.loader.jar.JarEntry.(JarEntry.java:55) at org.springframework.boot.loader.jar.JarFileEntries.getEntry(JarFileEntries.java:252) at org.springframework.boot.loader.jar.JarFileEntries.access$400(JarFileEntries.java:45) at org.springframework.boot.loader.jar.JarFileEntries$EntryIterator.next(JarFileEntries.java:302) at org.springframework.boot.loader.jar.JarFileEntries$EntryIterator.next(JarFileEntries.java:286) at org.springframework.boot.loader.jar.JarFile$2.nextElement(JarFile.java:196) at org.springframework.boot.loader.jar.JarFile$2.nextElement(JarFile.java:187) at org.springframework.boot.loader.archive.JarFileArchive$EntryIterator.next(JarFileArchive.java:186) at org.springframework.boot.loader.archive.JarFileArchive$EntryIterator.next(JarFileArchive.java:171) at org.springframework.boot.loader.archive.JarFileArchive.getNestedArchives(JarFileArchive.java:84) at org.springframework.boot.loader.ExecutableArchiveLauncher.getClassPathArchives(ExecutableArchiveLauncher.java:70) at org.springframework.boot.loader.Launcher.launch(Launcher.java:49) at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)

joeljeske commented 4 years ago

Interesting. Well, this may have been fixed for you if you still have the code to invoke single jar lying around.

I believe they changed the default date to Jan 1 2010, and could be confirmed by extracting any of the other jars in your output base.

https://github.com/bazelbuild/bazel/blob/46b285a8044d8c2dcf292f40b6b74bd99d66b430/src/tools/singlejar/output_jar.cc#L320

joeljeske commented 4 years ago

Do you still have the code for packaging with singlejar. I had some difficulty figuring out how to setup that dep pipeline properly to get singlejar and invoke it.

plaird commented 4 years ago

I think so. This was done in the Bazel 0.5.2 era, and we have moved the rule around a few times internally in Git repos. But I think I should be able to find it. I have it on my list, I will try to get to it today.

plaird commented 4 years ago

@joeljeske So the bad news is I don't think I ever had singlejar hooked up to the springboot rule. I also can't remember what I changed in the rule that worked around the timestamp problem above. Normally I put such details in bug reports, but I didn't in this case.

The good news is I implemented singlejar this evening, and it is now available as a branch. Since it is a big change, I am hoping you can verify it. I tested manually both by invoking the jar directly, and also with bazel run.

java -jar bazel-bin/samples/helloworld/helloworld.jar
bazel run samples/helloworld

I have also hooked it up to our build internally, and all of our services passed tests with the change. I think it is good to go.

Branch: https://github.com/salesforce/bazel-springboot-rule/tree/plaird/singlejar

WORKSPACE import:
git_repository(
    name = "bazel_springboot_rule",
    remote = "https://github.com/salesforce/bazel-springboot-rule.git",
    commit = "b4e065f1e706e6762cad022413b089789cd4a666"
)
plaird commented 4 years ago

This ran for 5 days in our internal CI and we did some actual deployments with it. Closing.