Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Enable tags to be pinned and overwritten tags to be tracked #83

Open
thespad opened this issue Jan 19, 2025 · 22 comments
Labels
enhancement New feature or request

Comments

@thespad
Copy link

thespad commented Jan 19, 2025

User stories

  • As a user I want to be able to ignore unrelated tags for a given image that cause it to be flagged as out of date

Feature description

For example I know that there's a newer postgres 17 tag, but I'm running postgres 15 and so I care about updates to that tag and can't/don't want to jump to 17.

This is especially common with databases but there are plenty of other situations where other tags may get pushed that you don't care about but still flag as "newer" versions.

Doesn't even need to be per image, just a global "only check for updates to the running tag" option would be sufficient

@thespad thespad added the enhancement New feature or request label Jan 19, 2025
@AlexGustafsson
Copy link
Owner

You would get more or less this behavior if you use a more granular image version, such as 16.0. Cupdate would recommend you updates on 16.x and if there are none, show you 17.x.

Please see an answer I gave to a similar issue: #74 (comment):

@edersong as a side note: I have pretty much the same case with an "old" mongo image that is stuck on 6.x.x, even though multiple newer majors are released. To solve that case I implemented "version tracks". Cupdate is aware of semver and will prioritize notifying you about updates on the same minor or major, depending on the granularity you've specified for your container (i.e. the 6.1.0 tag vs 6.1 vs just 6). So if you're running 6.0.0 and 6.0.1 is released, Cupdate will show that patch version as the latest version available to you, even if 7 is available. If you're at the end of a track, Cupdate will recommend the latest version again, despite being a new major.

I'm still looking at implementing an ignore label, but Cupdate does already try to help in some cases, making sure you don't miss for example a security patch on an otherwise not updated image version.

On that note, this "track" behavior could also be made configurable, for example by opting out of looking for new majors - marking 6.0.1 in the above case as the latest version as there's no newer 6.x.x version released.

[...]

@thespad
Copy link
Author

thespad commented Jan 19, 2025

It's the less that's the problem, something like this

Image

Isn't helpful to me because I don't want to update to 8.x, and by having it constantly showing as needing an update it trains me to ignore mongo, making it less likely that I'll notice when a 7.0 update is published.

It doesn't need an ignore option so much as an "only track this tag" option, essentially "I've pinned this for a reason, just tell me if the pinned tag gets an update".

@AlexGustafsson
Copy link
Owner

Would your use case be covered sufficiently by only recommending updates to mongo in this case if, let's say, 7.1 was released? That is, staying within the same major (a bit more detail in the last sentence of the quote in the previous comment):

On that note, this "track" behavior could also be made configurable, for example by opting out of looking for new majors - marking 6.0.1 in the above case as the latest version as there's no newer 6.x.x version released.

Cupdate relies on semantic versioning in tags to identify updates, meaning that if you were to ask Cupdate to ignore other tags, it wouldn't recognize a new push to the same tag as a new update. As a result, disabling checks for other tags would provide you with updates on vulnerabilities, graphs, etc., just not the image itself. Is that what you'd like to get out of Cupdate?

@thespad
Copy link
Author

thespad commented Jan 19, 2025

Mongo is a bad example because they have a stupid versioning system where x.0 is their LTS release and x.1, x.2, are short-term support releases, so there you probably wouldn't want to jump to 7.1 anyway, but for most software that wouldn't be an issue.

I haven't looked at your underlying logic but I had assumed you were tracking image manifest hashes (because otherwise any repo using a latest or other non-semver tag would never show updates being available) and so should be able to track when a tag updates on the remote registry regardless of semver state.

My primary point of comparison here is Diun. The additional contextual information that you're providing with Cupdate is useful but also most people (for better or worse) are using generic fixed tags (like latest) rather than semver so there probably needs to be a way to account for repos where the primary updating is fixed tags changing manifest SHA.

@AlexGustafsson
Copy link
Owner

AlexGustafsson commented Jan 19, 2025

Thanks for the clarification and sorry for the long answer.

Cupdate doesn't currently take the digest into consideration. For example, latest is always latest and considered up-to-date / managed by the OCI runtime. Similarly 8 is always assumed to be 8 even though the underlying image could be 8.1, but as you've noticed, Cupdate will check for updates to 9 in that case. For the time being I don't have the intention of supporting digests.

There are a few ways I can see to solve this issue.

  1. Use a reference with a tag containing at least the minor part. Make it configurable to not recommend a new "major" version. I.e.. let me stick to mongo:6.x or mongo:6.x.x.
  2. Make it configurable to freeze an image. I.e. don't bother checking for versions, I don't intend to update.
  3. Use a reference with a digest to communicate that the version is fixed. I.e. run mongo@sha256:.....
  4. Use a reference with a tag and digest to communicate that the version is fixed. I.e. run mongo:6@sha256:...

I intend to support option 1 as I think that aligns well with the current feature set and semantic versioning, as well as making hard-to-update services work better. I also intend on supporting option 3 as it's the standard way of freezing an image's version. I'm not convinced option 2 is necessary, it's probably better solved by option 3. Option 4 is similar to option 3 but contains a tag as well and could be used to check for changed digests (with some caveats).

So now, I have a few questions again to take the opportunity to understand your (and probably others') use case:

  • Would you consider using option 1 by running, for example, postgres:16.6 as opposed to postgres:16 or is that not acceptable in your case / with your workflow?
  • Do you use manifests like compose files, are the versions mentioned in them?
  • How do you update the image today? Are you using docker pull and then restarting the service, for example?

A bit of background/context:

I have had the intention of eventually supporting tracking image digests, mostly for vulnerability scanning (they could help with SBOMs) and to more correctly present the actual images in use. But digests come with a few issues that make them a bit cumbersome to implement and I haven't put in the required effort yet.

  • The digests are not readily available in all contexts of the Kubernetes APIs
  • The digests refer to platform-dependent manifests for multi-arch images

As most of Cupdate's code is made to be platform / runtime agnostic, the first issue has made digests a no-go. The other issue makes it quite cumbersome to use digests without assumptions. For example, in the case of remote hosts, not all hosts necessarily use the same architecture and would therefore not use the same manifests.

I agree that probably a lot of people are using tags like latest or just 8. But I believe (at least in most cases) doing so is an anti-pattern and against best practices. As such, it hasn't been prioritized. The target happy path for Cupdate is an image following OCI conventions with a tag following semantic versioning and where the underlying manifests don't change. Cupdate should still work outside of the happy path, but not all features might be useful or work out-of-the-box.

@thespad
Copy link
Author

thespad commented Jan 19, 2025

So FWIW ,in terms of Docker at least, options 3 and 4 are identical on the backend. If you provide a manifest hash the tag is ignored, thus all of these:

someguy/image:1.2.3@sha256:d31f2d4a88a0c5d98f6bf7f4725e228959b6d22dcd14d44c7a755521f007cd98
someguy/image@sha256:d31f2d4a88a0c5d98f6bf7f4725e228959b6d22dcd14d44c7a755521f007cd98
someguy/image:foo@sha256:d31f2d4a88a0c5d98f6bf7f4725e228959b6d22dcd14d44c7a755521f007cd98

Are the same underlying image, so it's really just a visual preference.

As for my own use cases I use a big mix of images; some use semver, some don't, some I pin to major or minor versions, some I use with a generic tag (either because they don't semver or because I'm intentionally running a bleeding edge release). All of my containers (in terms of anything long-running) are managed via compose. I'm not using Renovate or similar tools (mostly because of the above, I'd spend more time crafting ways to handle non- and almost-semver tags than I currently spend updating things), nor am I using anything like Watchtower to blindly auto-update (because I like having a functional environment).

I understand the complexities of working with multiple runtimes and dealing with multi-arch manifests and the difficulties that can bring in terms of tracking image updates across multiple hosts (or indeed hosts with qemu in use). I don't have any particular insight when it comes to k8s APIs as the bulk of my experience here is with docker registries and manifests.

That said, antipattern or not you're likely to get a lot of confused users if you aren't tracking updates to non-semver tags at all and don't make that very clear in your docs, because running latest is very popular, as is releasing software with weird versioning schemes that don't map to semver.

@AlexGustafsson
Copy link
Owner

AlexGustafsson commented Jan 20, 2025

Thank you for your answer and for sharing your thoughts and experiences. I'm tracking the necessary Kubernetes changes in #90. After that, digests should be trackable on both platforms. It would lay the groundwork for getting overwritten tags to work.

@AlexGustafsson AlexGustafsson changed the title Feature Request: Ignore unrelated repo tags Feature Request: Enable tags to be pinned and overwritten tags to be tracked Jan 20, 2025
@AlexGustafsson
Copy link
Owner

There's been a lot of related changed made recently and I think we're at a point where Cupdate will be able to handle digests properly in most cases. Feel free to try it out using the pr-92 tag. As image references will now contain a digest if possible, current entries will be removed and replaced as Cupdate processes the backlog of images.

The work on supporting freezing tags that do follow semver, at least in part, has not been started. But latest and things like that should work now. Getting 8.0 to work should "just" be a matter of enabling config to use that behavior despite looking like it's semver.

I'm not convinced that a global option is the way forward, but I'll definitely be looking at implementing it as a way to experiment with the behavior and for you to try out. It might just not be what ends up being the documented solution for your use case.

@thespad
Copy link
Author

thespad commented Jan 24, 2025

Checking out the PR image now, I am seeing some Unauthorised errors in the logs for manifest fetches:

cupdate  | {"time":"2025-01-23T23:49:42.175420432Z","level":"WARN","msg":"Job step failed","service.version":"development build","service.name":"cupdate","workflow":"Process image","job":"Get OCI information","step":"Get manifest","error":"oci: UNAUTHORIZED - authentication required"}
cupdate  | {"time":"2025-01-23T23:49:42.175460611Z","level":"WARN","msg":"Skipping job as dependent job failed","service.version":"development build","service.name":"cupdate","workflow":"Process image","job":"Get GitHub information","dependency":"oci"}
cupdate  | {"time":"2025-01-23T23:49:42.175475148Z","level":"WARN","msg":"Skipping job as dependent job failed","service.version":"development build","service.name":"cupdate","workflow":"Process image","job":"Get Docker Hub information","dependency":"oci"}
cupdate  | {"time":"2025-01-23T23:49:42.175482009Z","level":"WARN","msg":"Skipping job as dependent job failed","service.version":"development build","service.name":"cupdate","workflow":"Process image","job":"Get GitLab information","dependency":"oci"}
cupdate  | {"time":"2025-01-23T23:49:42.1754918Z","level":"WARN","msg":"Skipping job as dependent job failed","service.version":"development build","service.name":"cupdate","workflow":"Process image","job":"Get GHCR information","dependency":"oci"}
cupdate  | {"time":"2025-01-23T23:49:42.17550875Z","level":"WARN","msg":"Skipping job as dependent job failed","service.version":"development build","service.name":"cupdate","workflow":"Process image","job":"Get Quay information","dependency":"oci"}
cupdate  | {"time":"2025-01-23T23:49:42.175600022Z","level":"ERROR","msg":"Failed to run pipeline for image","service.version":"development build","service.name":"cupdate","reference":"lscr.io/linuxserver/unifi-network-application:8.6.9@sha256:f8403a6813b9e0e68fcf622a7f00dbdbd772dfd6a11277fedcb54b816e0eb801","error":"job failed due to one or more errors\nfailed to run job - dependent job failed: job failed due to one or more errors\nfailed to run job - dependent job failed: job failed due to one or more errors\nfailed to run job - dependent job failed: job failed due to one or more errors\nfailed to run job - dependent job failed: job failed due to one or more errors\nfailed to run job - dependent job failed: job failed due to one or more errors"}

For example, which manifests in the UI as

The latest version cannot be identified. This could be due to the image not being available, the registry not being supported, missing authentication or a temporary issue.

@thespad
Copy link
Author

thespad commented Jan 24, 2025

Additionally I'm seeing this

Image

Where the "old" version is v3.3@sha256:5dab096330e618b66dc98a3fa89ec6d1c493341d9da204318ce8b2fa21f88a3f and the "new" version is v3.3

@AlexGustafsson
Copy link
Owner

Thanks for taking a look. I've seen the issue with lscr.io locally as well. I don't think it's due to recent changes on our side. For me, it eventually started working after a few retries.

I've read that that registry mirrors to several backends such as GHCR. I've only ever seen GHCR be in use,so Cupdate makes that assumption for auth. But recently some requests seem to have been sent to another backend registry, thus causing the unauthorized issue. Unfortunately I haven't seen any documentation from them on what registries are in use or how a client can identify what registry will be in use. I think we'll need to handle lscr a bit differently, hopefully with something we can reuse for other registries, like using the www-authenticate header.

I've might have been too focused on non-semver tags and forgot to handle digests for new semver tags. I'll double check. Thanks again!

@thespad
Copy link
Author

thespad commented Jan 24, 2025

lscr uses ghcr as a primary backend, in principle it should only ever change if there's a prolonged ghcr outage (or some policy/entitlement change that renders them unviable going forward), and realistically would end up using Docker Hub in that situation (the images themselves are also mirrored to quay and gitlab but quay's performance and reliability are pretty dire and gitlab only retains the last 10k tags).

@AlexGustafsson
Copy link
Owner

AlexGustafsson commented Jan 24, 2025

I've might have been too focused on non-semver tags and forgot to handle digests for new semver tags.

That was the case. I'm now grabbing the manifest and digest of the latest semver tags as well. Should be available if you pull the pr-92 tag again.

Thanks for the information! I'll see if I can make sure that lscr.io works with the fallbacks as well.

Edit: I've improved the support for lscr.io. I've pushed a fix to main and rebased #92 on top of that. The pr-92 should contain the fix as well.

@thespad
Copy link
Author

thespad commented Jan 24, 2025

Looking much better with the most recent PR image, although there's a sorting issue where new latest tags don't get sorted to the top like new semver tags do.

There's also a very weird issue which isn't your problem per se, but is something to do with the docker library Traefik image and whatever is going on with their image pushes, because I have:

Image

So again possibly a sorting issue, but both of those v3.3's are SHA hashes that point to the same image, with the same attestation manifest but one is tagged and the other isn't. I can't actually update to traefik:v3.3@sha256:e8b170343bb1ab703a956049291ef0d951867bef39839c9b0d70eebda6b2ed29 because I already have traefik:v3.3@sha256:5dab096330e618b66dc98a3fa89ec6d1c493341d9da204318ce8b2fa21f88a3f locally and Docker sees them as the same image. Not the first time they've done something like this TBF and I don't think you can reasonably handle it, but I thought I'd mention it anyway.

@AlexGustafsson
Copy link
Owner

AlexGustafsson commented Jan 25, 2025

Thanks! I noticed the sorting issue as well (#92 (comment)). It should be fixed now. Again, available on the pr-92 tag.

As for the Traefik issue, I think this is an issue we can work around, but it requires knowledge of the target architecture.

Essentially, the digest you have locally, 5dab096330e618b66dc98a3fa89ec6d1c493341d9da204318ce8b2fa21f88a3f points to a "fat" manifest:

Local
{
  "manifests": [
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "amd64",
        "org.opencontainers.image.base.digest": "sha256:483f502c0e6aff6d80a807f25d3f88afa40439c29fdd2d21a0912e0f42db842a",
        "org.opencontainers.image.base.name": "alpine:3.21",
        "org.opencontainers.image.created": "2025-01-14T19:27:21Z",
        "org.opencontainers.image.revision": "4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b",
        "org.opencontainers.image.source": "https://github.com/traefik/traefik-library-image.git#4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b:v3.3/alpine",
        "org.opencontainers.image.url": "https://hub.docker.com/_/traefik",
        "org.opencontainers.image.version": "v3.3.2"
      },
      "digest": "sha256:81c7d4b7e69cc47f2d236c9ed9e9a4aa09794b57ddcc9650400a55177656e1b5",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      },
      "size": 1728
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "amd64",
        "vnd.docker.reference.digest": "sha256:81c7d4b7e69cc47f2d236c9ed9e9a4aa09794b57ddcc9650400a55177656e1b5",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "digest": "sha256:a0f65e21fdc8c97b8db5f960452ae4e54a047560b72d3f1e8d188634855775cb",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      },
      "size": 840
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "arm32v6",
        "org.opencontainers.image.base.digest": "sha256:c79529000bdf8bc63d5a64a4a6f20fc86a2d5f7d8fc8c68863243a3e05c2a136",
        "org.opencontainers.image.base.name": "alpine:3.21",
        "org.opencontainers.image.created": "2025-01-14T19:27:10Z",
        "org.opencontainers.image.revision": "4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b",
        "org.opencontainers.image.source": "https://github.com/traefik/traefik-library-image.git#4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b:v3.3/alpine",
        "org.opencontainers.image.url": "https://hub.docker.com/_/traefik",
        "org.opencontainers.image.version": "v3.3.2"
      },
      "digest": "sha256:353d92f2258bfe055145fe6496a72ae60ba9df7238d1f152821c7636fb557df8",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "arm",
        "os": "linux",
        "variant": "v6"
      },
      "size": 1730
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "arm32v6",
        "vnd.docker.reference.digest": "sha256:353d92f2258bfe055145fe6496a72ae60ba9df7238d1f152821c7636fb557df8",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "digest": "sha256:5e642e883f84ab165cd40b9200440eff266217d0e44c3ce72491824506222133",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      },
      "size": 567
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "arm64v8",
        "org.opencontainers.image.base.digest": "sha256:508c1b94e1d20635b3326ee2a28180102ffde2a13d5b72cdd1c4d4872ad2f83c",
        "org.opencontainers.image.base.name": "alpine:3.21",
        "org.opencontainers.image.created": "2025-01-08T22:06:53Z",
        "org.opencontainers.image.revision": "79eb99e8c991e149bfa5fd468bbbf2b0e1f2b16a",
        "org.opencontainers.image.source": "https://github.com/traefik/traefik-library-image.git#79eb99e8c991e149bfa5fd468bbbf2b0e1f2b16a:v3.3/alpine",
        "org.opencontainers.image.url": "https://hub.docker.com/_/traefik",
        "org.opencontainers.image.version": "v3.3.1"
      },
      "digest": "sha256:bd54fbba443e38a0ab67718c61d196762033a5c04a68b67aab0993e1cd92dac0",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "arm64",
        "os": "linux",
        "variant": "v8"
      },
      "size": 1730
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "arm64v8",
        "vnd.docker.reference.digest": "sha256:bd54fbba443e38a0ab67718c61d196762033a5c04a68b67aab0993e1cd92dac0",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "digest": "sha256:e62748540f6ca3a561ca4244d21b5e509df10547838a7a7ed606d7b73ade8364",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      },
      "size": 840
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "ppc64le",
        "org.opencontainers.image.base.digest": "sha256:23dbce23b88f36f6d84223fefa3a7786f231fcd07d8350184cc4428ca38429ac",
        "org.opencontainers.image.base.name": "alpine:3.21",
        "org.opencontainers.image.created": "2025-01-14T19:33:52Z",
        "org.opencontainers.image.revision": "4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b",
        "org.opencontainers.image.source": "https://github.com/traefik/traefik-library-image.git#4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b:v3.3/alpine",
        "org.opencontainers.image.url": "https://hub.docker.com/_/traefik",
        "org.opencontainers.image.version": "v3.3.2"
      },
      "digest": "sha256:87ad07007290254488dd9abd4aea2affbc5b5b13871f5dec8c79db48b3966015",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "ppc64le",
        "os": "linux"
      },
      "size": 1730
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "ppc64le",
        "vnd.docker.reference.digest": "sha256:87ad07007290254488dd9abd4aea2affbc5b5b13871f5dec8c79db48b3966015",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "digest": "sha256:438104def6c17c8de2883ea5f6b50b0c8bdb66fb3a5ff29cd6b7d8a1a64e447a",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      },
      "size": 840
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "riscv64",
        "org.opencontainers.image.base.digest": "sha256:f9d2da150ceeb695656d64b6376cbcf3389ab6aba1270beb080711c72b3ad474",
        "org.opencontainers.image.base.name": "alpine:3.21",
        "org.opencontainers.image.created": "2025-01-10T16:35:08Z",
        "org.opencontainers.image.revision": "79eb99e8c991e149bfa5fd468bbbf2b0e1f2b16a",
        "org.opencontainers.image.source": "https://github.com/traefik/traefik-library-image.git#79eb99e8c991e149bfa5fd468bbbf2b0e1f2b16a:v3.3/alpine",
        "org.opencontainers.image.url": "https://hub.docker.com/_/traefik",
        "org.opencontainers.image.version": "v3.3.1"
      },
      "digest": "sha256:1713090e026d380b332566aa2c906969b5fc6eb5bfd4de4e54a5888deefe81e3",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "riscv64",
        "os": "linux"
      },
      "size": 1730
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "riscv64",
        "vnd.docker.reference.digest": "sha256:1713090e026d380b332566aa2c906969b5fc6eb5bfd4de4e54a5888deefe81e3",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "digest": "sha256:70f1d1d9bb3ddd05da289c26ef2e68fbea7499d5b27514497f03f2e31fcfd275",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      },
      "size": 840
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "s390x",
        "org.opencontainers.image.base.digest": "sha256:6bb03952a007ae0ef13a803b7678dda46d5ab1187713228b1252b90e6a60eebe",
        "org.opencontainers.image.base.name": "alpine:3.21",
        "org.opencontainers.image.created": "2025-01-14T20:15:23Z",
        "org.opencontainers.image.revision": "4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b",
        "org.opencontainers.image.source": "https://github.com/traefik/traefik-library-image.git#4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b:v3.3/alpine",
        "org.opencontainers.image.url": "https://hub.docker.com/_/traefik",
        "org.opencontainers.image.version": "v3.3.2"
      },
      "digest": "sha256:361d003ecc29d072a0d110a9a091f08f628d9399449cdd78276eb35b017a2fdd",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "s390x",
        "os": "linux"
      },
      "size": 1728
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "s390x",
        "vnd.docker.reference.digest": "sha256:361d003ecc29d072a0d110a9a091f08f628d9399449cdd78276eb35b017a2fdd",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "digest": "sha256:9eaafb48782b81936ebc42fd49a855b617f4f731d29b5e6b3fcebe66e4da36ec",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      },
      "size": 840
    }
  ],
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "schemaVersion": 2
}

If I fetch the manifest for v3.3, however, I get a fat manifest with the digest e8b170343bb1ab703a956049291ef0d951867bef39839c9b0d70eebda6b2ed29 . Currently, as the manifests differ, Cupdate assumes they're out of sync.

Remote
{
  "manifests": [
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "amd64",
        "org.opencontainers.image.base.digest": "sha256:483f502c0e6aff6d80a807f25d3f88afa40439c29fdd2d21a0912e0f42db842a",
        "org.opencontainers.image.base.name": "alpine:3.21",
        "org.opencontainers.image.created": "2025-01-14T19:27:21Z",
        "org.opencontainers.image.revision": "4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b",
        "org.opencontainers.image.source": "https://github.com/traefik/traefik-library-image.git#4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b:v3.3/alpine",
        "org.opencontainers.image.url": "https://hub.docker.com/_/traefik",
        "org.opencontainers.image.version": "v3.3.2"
      },
      "digest": "sha256:81c7d4b7e69cc47f2d236c9ed9e9a4aa09794b57ddcc9650400a55177656e1b5",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      },
      "size": 1728
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "amd64",
        "vnd.docker.reference.digest": "sha256:81c7d4b7e69cc47f2d236c9ed9e9a4aa09794b57ddcc9650400a55177656e1b5",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "digest": "sha256:a0f65e21fdc8c97b8db5f960452ae4e54a047560b72d3f1e8d188634855775cb",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      },
      "size": 840
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "arm32v6",
        "org.opencontainers.image.base.digest": "sha256:c79529000bdf8bc63d5a64a4a6f20fc86a2d5f7d8fc8c68863243a3e05c2a136",
        "org.opencontainers.image.base.name": "alpine:3.21",
        "org.opencontainers.image.created": "2025-01-14T19:27:10Z",
        "org.opencontainers.image.revision": "4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b",
        "org.opencontainers.image.source": "https://github.com/traefik/traefik-library-image.git#4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b:v3.3/alpine",
        "org.opencontainers.image.url": "https://hub.docker.com/_/traefik",
        "org.opencontainers.image.version": "v3.3.2"
      },
      "digest": "sha256:353d92f2258bfe055145fe6496a72ae60ba9df7238d1f152821c7636fb557df8",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "arm",
        "os": "linux",
        "variant": "v6"
      },
      "size": 1730
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "arm32v6",
        "vnd.docker.reference.digest": "sha256:353d92f2258bfe055145fe6496a72ae60ba9df7238d1f152821c7636fb557df8",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "digest": "sha256:5e642e883f84ab165cd40b9200440eff266217d0e44c3ce72491824506222133",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      },
      "size": 567
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "arm64v8",
        "org.opencontainers.image.base.digest": "sha256:508c1b94e1d20635b3326ee2a28180102ffde2a13d5b72cdd1c4d4872ad2f83c",
        "org.opencontainers.image.base.name": "alpine:3.21",
        "org.opencontainers.image.created": "2025-01-15T01:26:11Z",
        "org.opencontainers.image.revision": "4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b",
        "org.opencontainers.image.source": "https://github.com/traefik/traefik-library-image.git#4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b:v3.3/alpine",
        "org.opencontainers.image.url": "https://hub.docker.com/_/traefik",
        "org.opencontainers.image.version": "v3.3.2"
      },
      "digest": "sha256:8630d704e299f03e2760d49758df08183789343bbda3ecc63e7db77804599156",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "arm64",
        "os": "linux",
        "variant": "v8"
      },
      "size": 1730
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "arm64v8",
        "vnd.docker.reference.digest": "sha256:8630d704e299f03e2760d49758df08183789343bbda3ecc63e7db77804599156",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "digest": "sha256:5ccf3be6fd3d4519838d787874315b3d58633399b4c6aa6f810ef6d8a88461c1",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      },
      "size": 840
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "ppc64le",
        "org.opencontainers.image.base.digest": "sha256:23dbce23b88f36f6d84223fefa3a7786f231fcd07d8350184cc4428ca38429ac",
        "org.opencontainers.image.base.name": "alpine:3.21",
        "org.opencontainers.image.created": "2025-01-14T19:33:52Z",
        "org.opencontainers.image.revision": "4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b",
        "org.opencontainers.image.source": "https://github.com/traefik/traefik-library-image.git#4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b:v3.3/alpine",
        "org.opencontainers.image.url": "https://hub.docker.com/_/traefik",
        "org.opencontainers.image.version": "v3.3.2"
      },
      "digest": "sha256:87ad07007290254488dd9abd4aea2affbc5b5b13871f5dec8c79db48b3966015",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "ppc64le",
        "os": "linux"
      },
      "size": 1730
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "ppc64le",
        "vnd.docker.reference.digest": "sha256:87ad07007290254488dd9abd4aea2affbc5b5b13871f5dec8c79db48b3966015",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "digest": "sha256:438104def6c17c8de2883ea5f6b50b0c8bdb66fb3a5ff29cd6b7d8a1a64e447a",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      },
      "size": 840
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "riscv64",
        "org.opencontainers.image.base.digest": "sha256:f9d2da150ceeb695656d64b6376cbcf3389ab6aba1270beb080711c72b3ad474",
        "org.opencontainers.image.base.name": "alpine:3.21",
        "org.opencontainers.image.created": "2025-01-14T20:26:10Z",
        "org.opencontainers.image.revision": "4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b",
        "org.opencontainers.image.source": "https://github.com/traefik/traefik-library-image.git#4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b:v3.3/alpine",
        "org.opencontainers.image.url": "https://hub.docker.com/_/traefik",
        "org.opencontainers.image.version": "v3.3.2"
      },
      "digest": "sha256:b47b9bf6a9b762e38526ce82e5b0626510e299a52e87319d018a23549a20f0dd",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "riscv64",
        "os": "linux"
      },
      "size": 1730
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "riscv64",
        "vnd.docker.reference.digest": "sha256:b47b9bf6a9b762e38526ce82e5b0626510e299a52e87319d018a23549a20f0dd",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "digest": "sha256:66a0aea548886a01a60df52dbb7c291f206d318870b6d7db6dddb015cffc7980",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      },
      "size": 840
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "s390x",
        "org.opencontainers.image.base.digest": "sha256:6bb03952a007ae0ef13a803b7678dda46d5ab1187713228b1252b90e6a60eebe",
        "org.opencontainers.image.base.name": "alpine:3.21",
        "org.opencontainers.image.created": "2025-01-14T20:15:23Z",
        "org.opencontainers.image.revision": "4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b",
        "org.opencontainers.image.source": "https://github.com/traefik/traefik-library-image.git#4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b:v3.3/alpine",
        "org.opencontainers.image.url": "https://hub.docker.com/_/traefik",
        "org.opencontainers.image.version": "v3.3.2"
      },
      "digest": "sha256:361d003ecc29d072a0d110a9a091f08f628d9399449cdd78276eb35b017a2fdd",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "s390x",
        "os": "linux"
      },
      "size": 1728
    },
    {
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "s390x",
        "vnd.docker.reference.digest": "sha256:361d003ecc29d072a0d110a9a091f08f628d9399449cdd78276eb35b017a2fdd",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "digest": "sha256:9eaafb48782b81936ebc42fd49a855b617f4f731d29b5e6b3fcebe66e4da36ec",
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      },
      "size": 840
    }
  ],
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "schemaVersion": 2
}

Diffing these we can see that the image index itself differ, but the images themselves only differ for some architectures. Assuming you're running amd64, the images are actually the same.

Diff
--- a/a
+++ b/a
@@ -72,13 +72,13 @@
         "com.docker.official-images.bashbrew.arch": "arm64v8",
         "org.opencontainers.image.base.digest": "sha256:508c1b94e1d20635b3326ee2a28180102ffde2a13d5b72cdd1c4d4872ad2f83c",
         "org.opencontainers.image.base.name": "alpine:3.21",
-        "org.opencontainers.image.created": "2025-01-08T22:06:53Z",
-        "org.opencontainers.image.revision": "79eb99e8c991e149bfa5fd468bbbf2b0e1f2b16a",
-        "org.opencontainers.image.source": "https://github.com/traefik/traefik-library-image.git#79eb99e8c991e149bfa5fd468bbbf2b0e1f2b16a:v3.3/alpine",
+        "org.opencontainers.image.created": "2025-01-15T01:26:11Z",
+        "org.opencontainers.image.revision": "4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b",
+        "org.opencontainers.image.source": "https://github.com/traefik/traefik-library-image.git#4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b:v3.3/alpine",
         "org.opencontainers.image.url": "https://hub.docker.com/_/traefik",
-        "org.opencontainers.image.version": "v3.3.1"
+        "org.opencontainers.image.version": "v3.3.2"
       },
-      "digest": "sha256:bd54fbba443e38a0ab67718c61d196762033a5c04a68b67aab0993e1cd92dac0",
+      "digest": "sha256:8630d704e299f03e2760d49758df08183789343bbda3ecc63e7db77804599156",
       "mediaType": "application/vnd.oci.image.manifest.v1+json",
       "platform": {
         "architecture": "arm64",
@@ -90,10 +90,10 @@
     {
       "annotations": {
         "com.docker.official-images.bashbrew.arch": "arm64v8",
-        "vnd.docker.reference.digest": "sha256:bd54fbba443e38a0ab67718c61d196762033a5c04a68b67aab0993e1cd92dac0",
+        "vnd.docker.reference.digest": "sha256:8630d704e299f03e2760d49758df08183789343bbda3ecc63e7db77804599156",
         "vnd.docker.reference.type": "attestation-manifest"
       },
-      "digest": "sha256:e62748540f6ca3a561ca4244d21b5e509df10547838a7a7ed606d7b73ade8364",
+      "digest": "sha256:5ccf3be6fd3d4519838d787874315b3d58633399b4c6aa6f810ef6d8a88461c1",
       "mediaType": "application/vnd.oci.image.manifest.v1+json",
       "platform": {
         "architecture": "unknown",
@@ -139,13 +139,13 @@
         "com.docker.official-images.bashbrew.arch": "riscv64",
         "org.opencontainers.image.base.digest": "sha256:f9d2da150ceeb695656d64b6376cbcf3389ab6aba1270beb080711c72b3ad474",
         "org.opencontainers.image.base.name": "alpine:3.21",
-        "org.opencontainers.image.created": "2025-01-10T16:35:08Z",
-        "org.opencontainers.image.revision": "79eb99e8c991e149bfa5fd468bbbf2b0e1f2b16a",
-        "org.opencontainers.image.source": "https://github.com/traefik/traefik-library-image.git#79eb99e8c991e149bfa5fd468bbbf2b0e1f2b16a:v3.3/alpine",
+        "org.opencontainers.image.created": "2025-01-14T20:26:10Z",
+        "org.opencontainers.image.revision": "4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b",
+        "org.opencontainers.image.source": "https://github.com/traefik/traefik-library-image.git#4fc0980f9d74f7c3be2ef4bf2513cb39b3d2226b:v3.3/alpine",
         "org.opencontainers.image.url": "https://hub.docker.com/_/traefik",
-        "org.opencontainers.image.version": "v3.3.1"
+        "org.opencontainers.image.version": "v3.3.2"
       },
-      "digest": "sha256:1713090e026d380b332566aa2c906969b5fc6eb5bfd4de4e54a5888deefe81e3",
+      "digest": "sha256:b47b9bf6a9b762e38526ce82e5b0626510e299a52e87319d018a23549a20f0dd",
       "mediaType": "application/vnd.oci.image.manifest.v1+json",
       "platform": {
         "architecture": "riscv64",
@@ -156,10 +156,10 @@
     {
       "annotations": {
         "com.docker.official-images.bashbrew.arch": "riscv64",
-        "vnd.docker.reference.digest": "sha256:1713090e026d380b332566aa2c906969b5fc6eb5bfd4de4e54a5888deefe81e3",
+        "vnd.docker.reference.digest": "sha256:b47b9bf6a9b762e38526ce82e5b0626510e299a52e87319d018a23549a20f0dd",
         "vnd.docker.reference.type": "attestation-manifest"
       },
-      "digest": "sha256:70f1d1d9bb3ddd05da289c26ef2e68fbea7499d5b27514497f03f2e31fcfd275",
+      "digest": "sha256:66a0aea548886a01a60df52dbb7c291f206d318870b6d7db6dddb015cffc7980",
       "mediaType": "application/vnd.oci.image.manifest.v1+json",
       "platform": {
         "architecture": "unknown",

So there are a few issues here:

  1. Traefik's image index changed, but only for some architectures. This makes it difficult to make assumptions about indexes.
  2. The digest found locally points to an entire image index, without specifying what image is actually in use. So Cupdate stores the index's digest as the digest in use. This can be the case across platforms, so it's something I'm already taking into consideration.
  3. The digest of the image index differs from the latest available one. As Docker didn't report the digest of an image within the manifest, Cupdate can't use it to see if the latest manifest at least contains the manifest in use. It has to make assumptions.

The first issue is a bit unfortunate as it requires us to solve the other issues.

The second issue we'll just have to deal with. It seems common enough across runtimes (Docker Engine, Containerd) to point to the index rather than actual image manifest.

The third issue would be solved by trying to apply the architecture of the engine to resolve the manifest like the runtimes would.

So to recap: This is a result of not knowing the architecture in use and not resolving the manifest of an index based on the architecture. I'd hope to solve this as a second step, though. So I think we'll need to have to work with this limitation for now.

@AlexGustafsson
Copy link
Owner

AlexGustafsson commented Jan 25, 2025

I think I've made a good change to the logic, that, in my testing, solves this specific case. Essentially I've implemented the platform-specific lookup, but left the necessary configuration for the future. In the meantime, when a platform is not known, Cupdate will play it safe and wait for all images in an index to change their digest when compared to the current index. In this specific case, it means that it will yield the same digest as the arm64 and amd64 image manifests' digests haven't changed between the two indexes. For now, this feels like a nice middle ground.

The new logic caught a case where an image author had overwritten all images of an index, for the same tag:

Image
rhasspy/wyoming-piper:1.5.0@sha256:103222f9522bf53fcd342ced9433f8666accca17603b583abcbc939962825c11
rhasspy/wyoming-piper:1.5.0@sha256:b6bf208855f26168790ed336ad16576b2fb290f31b51fb98aca496a45561516f

Again, pr-92 should be up-to-date with the latest changes.

@jacobslusser
Copy link

jacobslusser commented Jan 25, 2025

@AlexGustafsson I wanted to say thank you for working on this enhancement and encourage you to continue with it. I adopted cupdate a little over a week ago and in combination with Diun, I was looking for a workflow where I would get notified of new docker images and then open cupdate to see what the changes were. Like many others, I will sometimes use latest on images and initially cupdate was not behaving the way I expected because I got updates from Diun but saw no updates in cupdate.

Reading through this thread, I think I'm starting to understand the challenge, but would love continued effort in this area. What I was hoping cupdate would do was tell me -- regardless of the latest tag -- what was actually running and then tell me if there is a new version. That would be the goal. I updated to pr-92 just now and it is definitely an improvement. I can now usually see that there have been updates, even though it is still a little ambiguous as to what exactly that new version is or what the old/current version was.

Please continue with this enhancement. Ideal would be to see what latest actually means, both old a new version when displayed.

Image

Image

@AlexGustafsson
Copy link
Owner

@jacobslusser Thanks! I think I've gotten most of the features working that would be required to solve this. With the change Cupdate will try to track the digest for all images, so it will almost always be available in the UI. For now, the digest is only shown on hover, unless the image is "pinned" - i.e. you specified an image like so: dozzle@sha256:828aff1e96616ed10afdce55010b2226ad1872dd91eef8f214c4947e5f3f2d33.

How would you like it to be shown? I don't really know of an obvious solution. There's not really any "version" to show and the tag is the exactly same in both cases. We could show the digest at all times, but it's quite technical and probably not user-friendly, assuming that people are using tags like "latest" due to perceived convenience in the first place.

Here are a few screenshots of the current UI when nothing but a digest is specified. To be technically correct, the "sha256:" prefix is required. 10 hex digits are shown, with the rest being hidden as the fullt digest likely doesn't provide that much meaningful info. Any truncated text is always shown in full on hover.

Image Image

@AlexGustafsson
Copy link
Owner

Changes merged to main, should be available on the latest tag soon. @jacobslusser if you have ideas about how to show the digests, please open a feature request and we'll take it from there.

Thanks a lot, @thespad for the discussions and testing. I'll keep this issue open until #95 is solved. But for now I'll mark #90 and #94 as solved. Please ping me here, in those issues or just open new bug tickets if you come across any issues or limitations.

@jacobslusser
Copy link

@AlexGustafsson,

I can open a feature request, but not if the plan is to show the digest. TBH, for me - and I assume 99% of others -- the digest means next to zero for me. It would be better to leave it the way it is now than switch to showing digest.

It's your project, so you should follow your heart, but if it were me, I would ideally like to see this simple change:

  1. Always show version number for current and update
  2. Show tag defined in docker run/compose in brackets (or similar)

Example

Image

Image

In this arrangement, it tells me everything I want to know:

  • What my current version is
  • What the next version is in my upgrade path
  • Why I am being show this new version: because I am using a semver wildcard like v1.* or because I'm requesting latest

@AlexGustafsson
Copy link
Owner

AlexGustafsson commented Jan 27, 2025

Yeah, I agree the digest doesn't provide any helpful meaning. That's why we currently only show it when it's the only information we've got.

I'm not sure Cupdate will be able to take this route. There are no standard, well-established means to identify what "version" is actually used within an image. The tag is basically it. We try to use standard OCI annotations, but a lot of the community has not caught up. So the best we can do (and what we currently do) is to try to show semantic upgrades to tags that have the same amount of detail. So in your case above, let's say you've defined "v1" as the tag for Docker to use, Cupdate will look for changes made to "v1" itself (such as changes that are likely equivalent to v1.0.1, but we can't really tell) as well as for a v2. If you had specified v1.0.0 as the tag, Cupdate would show v1.0.1.

In the other case, latest is just another tag and doesn't hold any special meaning in the OCI world. The only convention being that it's likely to be overwritten and it's likely to track the main development branch closer. So essentially, if we get a tag, the closes thing to a version is basically the digest. So in your second image, Cupdate can't tell that you're running v1.11.1. It can just tell that "latest" was specified and that there is a newer "latest" available.

If possible for your workflow, could you try to use more explicit tags (v1.11.1 in this case)? That would give you the behavior of looking up actual "versions" in almost all cases.

I think you're suggestion makes a lot of sense from a user's standpoint, don't get me wrong, but I don't think it's currently technically feasible to do without assigning special meaning to latest and making a lot of assumptions of the data we can get from other sources than the registries and manifests themselves. It would probably only be supported for a small subset of images where we could correlate source code releases or something along those lines.

So bottom line is, you'll probably get a much better experience if you use "versioned" tags with as much detail as possible, v1.11.1 in the example above. There's only so much we can do for "latest" and other tags like it.

But your suggestion is good, so please feel free to open up a feature request so it can be tracked. I might have missed things or there may be better ways of solving it eventually.

@thespad
Copy link
Author

thespad commented Jan 27, 2025

Part of the problem is that AFAIK none of the registries support fetching tags by digest, so you can't do something like:

  • Image is running latest
  • Get digest
  • Query registry for all tags associated with digest
  • Image is also tagged v1.1.1, v1.1, etc.

Which means, like Alex said, you have to rely on the OCI labels or other metadata, which don't necessarily map to an actual tag somewhere to compare to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants