Obtain workload version from resource revision (without charmhub macaroon)

Release practice

We (Observability) create a tag for every charm release, for example:

Notice how the upstream-source, which is taken from charmcraft.yaml, is a major version tag. So, by looking at the github tag description above, or the output of charmcraft resource-revisions below,

$ charmcraft resource-revisions grafana-k8s grafana-image
Revision    Created at              Size  Architectures
71          2025-02-07T06:45:22Z    505B  amd64
70          2024-08-12T15:34:14Z    505B  amd64
69          2024-06-18T22:55:38Z    505B  all
68          2024-03-14T14:54:20Z    505B  all

we cannot immediately tell the workload version a given charm revision is associated with!

Obtain the workload version

To obtain the workload version, if we assume the OCI image has a org.opencontainers.image.version label (always true for rocks), then all we need to do is:

  1. Obtain charm id.
  2. Download the oci image resource blob and extract image id, username and password.
  3. skopeo inspect the image to extract the image version.

In a script it may look like this:

#!/bin/bash

charm_name="grafana-k8s"
resource_rev="71"

# The following vars must have a match on charmhub for the
# API request to go through, but they don't matter themselves
# because all we need is the charm id.
channel="1/stable"
arch="amd64"
channel="20.04"


# OBTAIN CHARM ID
# The "action" part is a required stand-in. Didn't know how to render it better.
charm_id=$(curl -sfL -XPOST --header "Content-Type: application/json" -d "{
  \"context\": [],
  \"actions\": [
    {
      \"action\": \"install\",
      \"instance-key\": \"this-is-a-test-for-resources-lookup-2025-06-11\",
      \"name\": \"$charm_name\",
      \"channel\": \"$channel\",
      \"base\": {
        \"architecture\": \"$arch\",
        \"name\": \"ubuntu\",
        \"channel\": \"$channel\"
      }
    }
  ],
  \"fields\": [\"id\"]
}" https://api.charmhub.io/v2/charms/refresh | jq -r '.results[0].id')


# PROCESS OCI IMAGE RESOURCE BLOB
oci_image_resource_blob=$(curl -sfL "https://api.charmhub.io/api/v1/resources/download/charm_${charm_id}.grafana-image_${resource_rev}")
image_id=$(echo $oci_image_resource_blob | jq -r '.ImageName')
username=$(echo $oci_image_resource_blob | jq -r '.Username')
password=$(echo $oci_image_resource_blob | jq -r '.Password')


# EXTRACT WORKLOAD VERSION
skopeo inspect --creds "${username}:${password}" "docker://${image_id}" | jq -r '.Labels."org.opencontainers.image.version"'

Use cases

  • The image we test in the release CI is not necessarily the same image the charm is released with, because the major version tag may have been updated in between. This way we will at least have a true record on the tag description.
  • When oci-factory / ROCKsBot reports vulnerabilities in upstream applications, this is a quick way to determine which resource revisions, and hence charm revisions, are affected.

Thanks to @lengau @verterok @taurus for the guidance!

See also

1 Like

I would be curious to see what the result (even a condensed version) of skopeo inspect looks like. Especially since it was the whole purpose of this post.

Fair :slight_smile: here it is:

{
  "Name": "registry.jujucharms.com/charm/h71m6jk2jeap1qu5lv9nv5mplqayr91q34lqp/grafana-image",
  "Digest": "sha256:fa57a72ee36debd46483554cbc6eb97493918c7be33b2339ff2a093b33d222e4",
  "RepoTags": [
    "latest"
  ],
  "Created": "2024-02-27T18:52:59.070788584Z",
  "DockerVersion": "",
  "Labels": {
    "org.opencontainers.image.base.digest": "722b3bddfe96b95441f626cf94974d79213090ecbd16954f71d7c080fb413561",
    "org.opencontainers.image.created": "2024-03-14T12:34:54.119790+00:00",
    "org.opencontainers.image.licenses": "AGPL-3.0",
    "org.opencontainers.image.ref.name": "grafana",
    "org.opencontainers.image.title": "grafana",
    "org.opencontainers.image.version": "9.5.3"
  },
  "Architecture": "amd64",
  "Os": "linux",
  "Layers": [
    "sha256:bccd10f490ab0f3fba61b193d1b80af91b17ca9bdca9768a16ed05ce16552fcb",
    "sha256:a7b11651829b89738d459492039dbc3ada2c39cbff6cb3f49e6caf756541622f",
    "sha256:2b9099bc4c904f8752ab3b0e0a1f80b07b1b35d87b8d699408b87815d3915281",
    "sha256:0f55832a1d63298b407eba20717ade31850921a4b9a705b2bd9988b5f268d4a4"
  ],
  "LayersData": [
    {
      "MIMEType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "Digest": "sha256:bccd10f490ab0f3fba61b193d1b80af91b17ca9bdca9768a16ed05ce16552fcb",
      "Size": 29538961,
      "Annotations": null
    },
    {
      "MIMEType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "Digest": "sha256:a7b11651829b89738d459492039dbc3ada2c39cbff6cb3f49e6caf756541622f",
      "Size": 92811300,
      "Annotations": null
    },
    {
      "MIMEType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "Digest": "sha256:2b9099bc4c904f8752ab3b0e0a1f80b07b1b35d87b8d699408b87815d3915281",
      "Size": 657,
      "Annotations": null
    },
    {
      "MIMEType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "Digest": "sha256:0f55832a1d63298b407eba20717ade31850921a4b9a705b2bd9988b5f268d4a4",
      "Size": 428,
      "Annotations": null
    }
  ],
  "Env": [
    "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
  ]
}