oras icon indicating copy to clipboard operation
oras copied to clipboard

bug: discrepancy between reference parsing using oras

Open Skarlso opened this issue 8 months ago • 11 comments

What happened in your environment?

Hey Peeps! :)

We found an interesting conundrum when dealing with oci layers and outlines on disk with --oci-layout.

It looks like, the parsing of the Ref on the cli for oras doesn't follow how a tag could look like? A tag accepts an absolute reference value but here it needs to be relative.

What am I talking about?

I'm talking about this function: https://github.com/oras-project/oras/blob/6f04cf05b29fa8eb7dd0cc0b636d45fa716911d1/cmd/oras/internal/option/target.go#L133

And then this: https://github.com/oras-project/oras/blob/main/cmd/oras/internal/fileref/unix.go#L25

And here is the tag ref grammar: https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys

The reproducing steps:

oras cp --to-oci-layout ghcr.io/stefanprodan/podinfo:6.8.0 ghcr.io/stefanprodan/podinfo:6.8.0
oras manifest fetch --oci-layout ./:6.8.0
# edit the `index.json` such that the annotation for index is the full ref: annotations: ["org.opencontainers.image.ref.name":"ghcr.io/stefanprodan/podinfo:6.8.0"]
oras manifest fetch --oci-layout ./:ghcr.io/stefanprodan/podinfo:6.8.0 # this now errors even though technically, it should be correct
➜ oras manifest fetch --oci-layout ./:ghcr.io/stefanprodan/podinfo:6.8.0
Error: invalid argument "./:ghcr.io/stefanprodan/podinfo:6.8.0": failed to find path "./:ghcr.io/stefanprodan/podinfo": stat ./:ghcr.io/stefanprodan/podinfo: no such file or directory

throws errors because it's biting off the end of the absolute path and then looks for that directory.

It's worth mentioning that the library does work. So there is some weirdness somewhere. :)

If this is indeed a discrepancy, we are fully committed to provide a fix as well. So we are going to do the work. :)

Thanks :bow:

What did you expect to happen?

Parsing of the reference to be working with relative and absolute path as well.

How can we reproduce it?

oras cp --to-oci-layout ghcr.io/stefanprodan/podinfo:6.8.0 ghcr.io/stefanprodan/podinfo:6.8.0
oras manifest fetch --oci-layout ./:6.8.0
# edit the `index.json` such that the annotation for index is the full ref: annotations: ["org.opencontainers.image.ref.name":"ghcr.io/stefanprodan/podinfo:6.8.0"]
oras manifest fetch --oci-layout ./:ghcr.io/stefanprodan/podinfo:6.8.0 # this now errors even though technically, it should be correct
➜ oras manifest fetch --oci-layout ./:ghcr.io/stefanprodan/podinfo:6.8.0
Error: invalid argument "./:ghcr.io/stefanprodan/podinfo:6.8.0": failed to find path "./:ghcr.io/stefanprodan/podinfo": stat ./:ghcr.io/stefanprodan/podinfo: no such file or directory

What is the version of your ORAS CLI?

➜ oras version Version: 1.2.2+Homebrew Go version: go1.23.4

What is your OS environment?

osx, linux

Are you willing to submit PRs to fix it?

  • [x] Yes, I am willing to fix it.

Skarlso avatar Apr 03 '25 08:04 Skarlso

Why not?

oras manifest fetch --oci-layout ./ghcr.io/stefanprodan/podinfo:6.8.0

TerryHowe avatar Apr 03 '25 10:04 TerryHowe

oras manifest fetch --oci-layout ./ghcr.io/stefanprodan/podinfo:6.8.0
Error: invalid argument "./ghcr.io/stefanprodan/podinfo:6.8.0": failed to find path "./ghcr.io/stefanprodan/podinfo": stat ./ghcr.io/stefanprodan/podinfo: no such file or directory

Skarlso avatar Apr 03 '25 10:04 Skarlso

I was confused on why you had : in the path and your instructions would not put that in the path.

After I modify the index.json, I get

Error: failed to fetch the content of "./ghcr.io/stefanprodan/podinfo:6.8.0": not found

but not the "failed to find path" that you got. The error message I got is not very helpful, because it is probably something like a mismatch on expected reference.

TerryHowe avatar Apr 03 '25 11:04 TerryHowe

What is the reason to modify the index.json? Both references should be there:

{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:6c1975b871efb327528c84d46d38e6dd7906eecee6402bc270eeb7f1b1a506df","size":2385,"annotations":{"org.opencontainers.image.ref.name":"6.8.0"}},
{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:6c1975b871efb327528c84d46d38e6dd7906eecee6402bc270eeb7f1b1a506df","size":2385,"annotations":{"org.opencontainers.image.ref.name":"ghcr.io/stefanprodan/podinfo:6.8.0"}},

TerryHowe avatar Apr 03 '25 12:04 TerryHowe

What do you mean by "should be there"? Do you mean that oras fetch should have put in both...? Because right now, that's all the index.json file is:

{
  "schemaVersion": 2,
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.index.v1+json",
      "digest": "sha256:6c1975b871efb327528c84d46d38e6dd7906eecee6402bc270eeb7f1b1a506df",
      "size": 2385,
      "annotations": {
        "org.opencontainers.image.ref.name": "6.8.0"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:365b1952433cbb4cf0e8bea4bb9b243096f6f987f7e8b32ae0e3f5a480a33650",
      "size": 840,
      "annotations": {
        "vnd.docker.reference.digest": "sha256:214be3e0fcce3be28e948e838eea34d6e3d9b24ce0689d7b879477a5d4b709d1",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:4468906882c4ff094af439835d5a76719cb6feea98bb8ea087fc8964f3568610",
      "size": 840,
      "annotations": {
        "vnd.docker.reference.digest": "sha256:076337b0f03bf33430505b37898390a6090efea255222a647d10a05ed2926996",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:47190298cb07dbb845dd29445b827dbe4a1cb4d92157182343e40c8b60319ffb",
      "size": 840,
      "annotations": {
        "vnd.docker.reference.digest": "sha256:e17bf402cd8ff41a322d12dc013678bf03e3558909c5a3a32388cbe157292f13",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:076337b0f03bf33430505b37898390a6090efea255222a647d10a05ed2926996",
      "size": 1627,
      "platform": {
        "architecture": "arm",
        "os": "linux",
        "variant": "v7"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:214be3e0fcce3be28e948e838eea34d6e3d9b24ce0689d7b879477a5d4b709d1",
      "size": 1627,
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:e17bf402cd8ff41a322d12dc013678bf03e3558909c5a3a32388cbe157292f13",
      "size": 1625,
      "platform": {
        "architecture": "arm64",
        "os": "linux"
      }
    }
  ]
}

Skarlso avatar Apr 03 '25 14:04 Skarlso

I can reproduce the error. It is caused by oras-cli only treating the string after the last : as tag. It does not consider cases in which a tag contains :. But this may be tricky to fix, as directory path is also allowed to have :.

wangxiaoxuan273 avatar Apr 08 '25 03:04 wangxiaoxuan273

Nice, thanks very much.

Skarlso avatar Apr 08 '25 05:04 Skarlso

After running oras cp --to-oci-layout ghcr.io/stefanprodan/podinfo:6.8.0 ghcr.io/stefanprodan/podinfo:6.8.0, I got the following directory structure:

$ tree .
.
└── ghcr.io
    └── stefanprodan
        └── podinfo
            ├── blobs
            │   └── sha256
            │       ├── 0654843d1f45e2e689e29a7c05c5affade2084a76bf03e63ada6c5f33d32d590
            │       ├── 076337b0f03bf33430505b37898390a6090efea255222a647d10a05ed2926996
            │       ├── 0aae13986da760b285bdd0e26d1152d9207bd0ecf29b3747e9f9dd7b67042513
            │       ├── 0c28a55df19d49ef125e83bb7d40ea3360e6f0b916e35bc5ea7a0ec431afd359
            │       ├── 214be3e0fcce3be28e948e838eea34d6e3d9b24ce0689d7b879477a5d4b709d1
            │       ├── 2b0fcbb1f62fc2623534a1d970004f3fc9e1080271e6fb165bc5e88fa35a1469
            │       ├── 2bf3ba7b8ebd88461be2d8dc346e0e3fbfef2d71ea40955c828adf612958195d
            │       ├── 3600fdc6126f9d836b5428dea7bed7ef2cc61ccd84f62069040937f32edcd7bb
            │       ├── 365b1952433cbb4cf0e8bea4bb9b243096f6f987f7e8b32ae0e3f5a480a33650
            │       ├── 3f9c6681619de458b6ec5659835a6dd1f2ebcbc9ba88320f1484331d7518e893
            │       ├── 4468906882c4ff094af439835d5a76719cb6feea98bb8ea087fc8964f3568610
            │       ├── 47190298cb07dbb845dd29445b827dbe4a1cb4d92157182343e40c8b60319ffb
            │       ├── 47ca6f5331bad2a49bad341fecbd93ba330f00c1f86b5f5d8f5a512a45e8a9e4
            │       ├── 4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1
            │       ├── 581143f6696816ad3afdfb7ebf44d584b38d07f0ae0a700921d8eebdfa3892a3
            │       ├── 6089034c246b857670b84d82ba3c807ea1b5d9cafe2569c99d1c70bd9c94c278
            │       ├── 6c1975b871efb327528c84d46d38e6dd7906eecee6402bc270eeb7f1b1a506df
            │       ├── 6e771e15690e2fabf2332d3a3b744495411d6e0b00b2aea64419b58b0066cf81
            │       ├── 79fd453b97f7a47f77272088d76d74c36e4bdc04c44586bfe9cc393521c2da84
            │       ├── 7c5533c09b9f28a5d1c2a8d7cda288d456d9ac33b037de17863f6fb664220163
            │       ├── 85f3b18f9f5a8655db86c6dfb02bb01011ffef63d10a173843c5c65c3e9137b7
            │       ├── 897f04f58f3cbe605f37df9562c3d3a9a864fae29f09268e66511c82de898a2a
            │       ├── 8e30b3ee087cc5a1b7a083cbe5fee74670ad75583ace86edcd110abbe8ea3cec
            │       ├── 9a3025d851d81ca41aa912a2cfadce13442e4f64a9c36724798783dee0170bfc
            │       ├── 9b6b71e6a17b544e522350b2a912c65a020906a220d6ba4dcd7e752e7bae6165
            │       ├── a44ca8345dfa31ae76d31de00f88a441a37962adbcbc6e163bae500a7545052b
            │       ├── a917e6f592d4bb4e2073676b0ff92ec4c378656581fd8bfeb967f6b7db5fe17d
            │       ├── bb9c56543049d2edcadd9fc238a3112aa7247c982a9b0902e15c1bf83e7a4fc8
            │       ├── c11d0af14aad5a76c08e62982645c21b17b24353a54dfe23d4cd0037b03854e6
            │       ├── d5f961ac2905042f3b88d80870b5cbe9c953c1ace58a09b869e7f7ac89e7bf3c
            │       ├── d8a81c3b621a13e6aa942c4b8fc5b33e43ee7ac03782ca0634b931355431d40f
            │       ├── dbe87e0a233a21e1a1fc7c24e940a34c6197816785af15a9b946f961bf655094
            │       ├── dd1a152f3ff0cdf76983df685418f4eed2986fc43771d04c2189b52763d600c6
            │       ├── e17bf402cd8ff41a322d12dc013678bf03e3558909c5a3a32388cbe157292f13
            │       ├── e4d09708fd762b84825c47fac2eaf352758e6252a605c9ac70cab6fa2860badd
            │       ├── f18232174bc91741fdf3da96d85011092101a032a93a388b79e99e69c2d5c870
            │       └── f9eb9faa9ed816b9a2744c7d6d97db3f638e3cad4d0a527ddbcd208b931518a1
            ├── index.json
            ├── ingest
            └── oci-layout

In this structure, the root directory of the OCI layout is ghcr.io/stefanprodan/podinfo, with ghcr.io and stefanprodan as parent directories.

With this, I'm able to get the manifest content by running:

oras manifest fetch --oci-layout ghcr.io/stefanprodan/podinfo:6.8.0

or

oras manifest fetch 6.8.0 --oci-layout-path ghcr.io/stefanprodan/podinfo

Differences between the two commands:

  1. The first command treats the string after : as the tag and everything before : as the path to the OCI layout root.
  2. The second command treats 6.8.0 as the target tag and the parameter of --oci-layout-path as the OCI layout root.

To check the index.json, I run:

cat ghcr.io/stefanprodan/podinfo/index.json | jq .

The output was:

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.index.v1+json",
      "digest": "sha256:6c1975b871efb327528c84d46d38e6dd7906eecee6402bc270eeb7f1b1a506df",
      "size": 2385,
      "annotations": {
        "org.opencontainers.image.ref.name": "6.8.0"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:4468906882c4ff094af439835d5a76719cb6feea98bb8ea087fc8964f3568610",
      "size": 840,
      "annotations": {
        "vnd.docker.reference.digest": "sha256:076337b0f03bf33430505b37898390a6090efea255222a647d10a05ed2926996",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:47190298cb07dbb845dd29445b827dbe4a1cb4d92157182343e40c8b60319ffb",
      "size": 840,
      "annotations": {
        "vnd.docker.reference.digest": "sha256:e17bf402cd8ff41a322d12dc013678bf03e3558909c5a3a32388cbe157292f13",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:e17bf402cd8ff41a322d12dc013678bf03e3558909c5a3a32388cbe157292f13",
      "size": 1625,
      "platform": {
        "architecture": "arm64",
        "os": "linux"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:214be3e0fcce3be28e948e838eea34d6e3d9b24ce0689d7b879477a5d4b709d1",
      "size": 1627,
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:076337b0f03bf33430505b37898390a6090efea255222a647d10a05ed2926996",
      "size": 1627,
      "platform": {
        "architecture": "arm",
        "os": "linux",
        "variant": "v7"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:365b1952433cbb4cf0e8bea4bb9b243096f6f987f7e8b32ae0e3f5a480a33650",
      "size": 840,
      "annotations": {
        "vnd.docker.reference.digest": "sha256:214be3e0fcce3be28e948e838eea34d6e3d9b24ce0689d7b879477a5d4b709d1",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      }
    }
  ]
}

To use the full reference ghcr.io/stefanprodan/podinfo:6.8.0 as a tag, I run:

oras tag 6.8.0 ghcr.io/stefanprodan/podinfo:6.8.0 --oci-layout-path ghcr.io/stefanprodan/podinfo

The index.json will then become:

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.index.v1+json",
      "digest": "sha256:6c1975b871efb327528c84d46d38e6dd7906eecee6402bc270eeb7f1b1a506df",
      "size": 2385,
      "annotations": {
        "org.opencontainers.image.ref.name": "ghcr.io/stefanprodan/podinfo:6.8.0"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.index.v1+json",
      "digest": "sha256:6c1975b871efb327528c84d46d38e6dd7906eecee6402bc270eeb7f1b1a506df",
      "size": 2385,
      "annotations": {
        "org.opencontainers.image.ref.name": "6.8.0"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:076337b0f03bf33430505b37898390a6090efea255222a647d10a05ed2926996",
      "size": 1627,
      "platform": {
        "architecture": "arm",
        "os": "linux",
        "variant": "v7"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:365b1952433cbb4cf0e8bea4bb9b243096f6f987f7e8b32ae0e3f5a480a33650",
      "size": 840,
      "annotations": {
        "vnd.docker.reference.digest": "sha256:214be3e0fcce3be28e948e838eea34d6e3d9b24ce0689d7b879477a5d4b709d1",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:4468906882c4ff094af439835d5a76719cb6feea98bb8ea087fc8964f3568610",
      "size": 840,
      "annotations": {
        "vnd.docker.reference.digest": "sha256:076337b0f03bf33430505b37898390a6090efea255222a647d10a05ed2926996",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:47190298cb07dbb845dd29445b827dbe4a1cb4d92157182343e40c8b60319ffb",
      "size": 840,
      "annotations": {
        "vnd.docker.reference.digest": "sha256:e17bf402cd8ff41a322d12dc013678bf03e3558909c5a3a32388cbe157292f13",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:e17bf402cd8ff41a322d12dc013678bf03e3558909c5a3a32388cbe157292f13",
      "size": 1625,
      "platform": {
        "architecture": "arm64",
        "os": "linux"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:214be3e0fcce3be28e948e838eea34d6e3d9b24ce0689d7b879477a5d4b709d1",
      "size": 1627,
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    }
  ]
}

This allows me to get the same manifest by running:

oras manifest fetch ghcr.io/stefanprodan/podinfo:6.8.0 --oci-layout-path ghcr.io/stefanprodan/podinfo

@Skarlso Does the same flow work for you? 🤔

Wwwsylvia avatar Apr 10 '25 10:04 Wwwsylvia

Problem is that we are unsure if we are even using things appropriately. That folder structure is actually not OCI compliant, right? It's just something ORAS did. The library store interface only works with a root in the filesystem. But can't handle multiple roots.

https://github.com/oras-project/oras-go/blob/main/content/oci/readonlyoci.go and https://github.com/oras-project/oras-go/blob/main/content/file/file.go do not support this behavior unless we misunderstand how we are supposed to use it together with this file structure.

If you have a better example or a guide in how to do that, we are also open to that. Maybe we are doing something wrong. :)

Skarlso avatar Apr 10 '25 14:04 Skarlso

For the command:

oras cp --to-oci-layout ghcr.io/stefanprodan/podinfo:6.8.0 ghcr.io/stefanprodan/podinfo:6.8.0

The target ghcr.io/stefanprodan/podinfo:6.8.0 is interpreted with everything before : as the path and everything after : as the target tag. This is why a directory structure is created. Essentially, ghcr.io/stefanprodan/podinfo is just a path to the root podinfo and doesn't have any special meaning—it's similar to foo/bar/podinfo or ./podinfo.

To avoid this, you can use a simpler path for the OCI layout folder:

oras cp ghcr.io/stefanprodan/podinfo:6.8.0 podinfo:6.8.0 --to-oci-layout

Here, the target OCI root is podinfo and the target tag is 6.8.0.

Alternatively, you can use:

oras cp ghcr.io/stefanprodan/podinfo:6.8.0 ghcr.io/stefanprodan/podinfo:6.8.0 --oci-layout-path podinfo

In this case, the target OCI root is podinfo and the target tag is the full reference ghcr.io/stefanprodan/podinfo:6.8.0.

Did this answer your question? Are you using CLI or SDK? 🤔

Wwwsylvia avatar Apr 11 '25 02:04 Wwwsylvia

Basically the same issue: #1505

wangxiaoxuan273 avatar May 19 '25 02:05 wangxiaoxuan273

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 30 days.

github-actions[bot] avatar Jul 19 '25 02:07 github-actions[bot]

Ah great. :D I think that fixes our use case as well. I'm so sorry for being absent on this bug. 🙇 Not very polite from me. :/

Skarlso avatar Jul 30 '25 05:07 Skarlso

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 30 days.

github-actions[bot] avatar Sep 30 '25 02:09 github-actions[bot]

This issue was closed because it has been stalled for 30 days with no activity.

github-actions[bot] avatar Oct 30 '25 02:10 github-actions[bot]