fix(oci): resolve artifact created time from image config - #6520
Conversation
|
This is a much better approach than the previous |
|
Thanks for the review @krrish175-byte! 🙏 Heads-up on CI for whoever picks this up: the
A re-run of that job should go green. Happy to rebase if preferred. cc @evankanderson |
| // configuration, which records the real build time. Unlike the previous | ||
| // time.Now() fallback, the resolved value always reflects the image itself: a |
There was a problem hiding this comment.
Generally, it doesn't make sense to have comments referencing code that no longer exists after this PR.
The one exception (which this does not fall into) is if the current code has to maintain a certain behavior when working with data that might have been produced by old code. An example of this might be // older code did not fill in Foo, even though that's now enforced by Validate()
There was a problem hiding this comment.
Good point, removed. The doc comment no longer references the old time.Now() fallback, it just describes the current behavior (annotation first, then the image config Created field, with zero/epoch preserved). Pushed in 30c3425.
| return createdAt, nil | ||
| } | ||
|
|
||
| cfg, err := configFile() |
There was a problem hiding this comment.
The previous code had comments about handling multi-architecture images. Have you tested this with an image like ghcr.io/mindersec/minder/server:latest?
There was a problem hiding this comment.
Tested with ghcr.io/mindersec/minder/server:latest, which is a 2-platform index (linux/amd64 + linux/arm64).
remote.Image resolves the index down to a single-platform image (amd64 by default), so img.Manifest() and img.ConfigFile() both run against that concrete child image, not the index. That is the per-architecture config the old FIXME was after.
For that image the created annotation is present, so it returns 2026-04-23T11:01:57Z from the annotation. With the annotation missing it would read Created from the resolved child config. Same behavior on alpine:latest.
evankanderson
left a comment
There was a problem hiding this comment.
Thanks for checking that codepath! It looks like we'll probably throw an error for multi-architecture images that don't include amd64/linux, but I think that still puts us in a better position than today. (And we can find this conversation in the commit history if we wonder why we stopped at this point....)
|
The security scan is failing on unrelated (new) Go Nice fix, very clean, just waiting for the rest of CI to go green. |
Summary
GetArtifactVersionsfell back totime.Now()when an image manifest lacked theorg.opencontainers.image.createdannotation (a// FIXME: This is a hack), soartifacts without that annotation were recorded as "created right now", silently
breaking age-based filtering and lifecycle policies.
This reads the real build time from the image config blob instead: it prefers the
manifest annotation and, when absent, falls back to the image config's
Createdfield (
img.ConfigFile().Created). A zero or Unix-epoch timestamp is a legitimatereproducible-build value and is preserved as-is rather than overwritten with the
current time.
The created-date resolution is extracted into a pure helper (
resolveCreatedAt) soit is unit-testable without a registry, and a
getImagehelper lets the same fetchedimage serve both the manifest and the config lookup.
Note: for a multi-arch image index lacking the annotation, the config now resolves to
the default platform's build time (or surfaces a config-read error) where the old hack
always returned
time.Now().Fixes #6490
Testing
internal/providers/oci/oci_test.go: table-driven unit test forresolveCreatedAt(annotation present / invalid / absent→config / epoch preserved / zero preserved / config error).
go test ./internal/providers/oci/...— passgo vet ./internal/providers/oci/...— cleangolangci-lint run ./internal/providers/oci/...— 0 issues