mirror of
https://github.com/opencontainers/umoci.git
synced 2026-02-06 21:44:54 +01:00
stat: include manifest information
This information is arguably a bit too "internal" but given that tools like [1] exist now, maybe just providing this information anyway could be useful. (Our tests would benefit from being able to pull out the digests of the configs and layers directly.) [1]: https://oci.dag.dev/ Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
This commit is contained in:
@@ -41,6 +41,22 @@ function digest_to_path() {
|
||||
statFile="$(setup_tmpdir)/stat"
|
||||
echo "$output" > "$statFile"
|
||||
|
||||
# .manifest.descriptor should describe a config blob
|
||||
sane_run jq -SMr '.manifest.descriptor.mediaType' "$statFile"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == "application/vnd.oci.image.manifest.v1+json" ]]
|
||||
|
||||
# .manifest.blob should match .manifest.descriptor data
|
||||
sane_run jq -SMr '.manifest.descriptor.digest' "$statFile"
|
||||
[ "$status" -eq 0 ]
|
||||
manifest_digest="$output"
|
||||
sane_run jq -SMr '.manifest.blob' "$statFile"
|
||||
[ "$status" -eq 0 ]
|
||||
manifest_data="$output"
|
||||
sane_run jq -SMr '.' "$(digest_to_path "$IMAGE" "$manifest_digest")"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == "$manifest_data" ]]
|
||||
|
||||
# .config.descriptor should describe a config blob
|
||||
sane_run jq -SMr '.config.descriptor.mediaType' "$statFile"
|
||||
[ "$status" -eq 0 ]
|
||||
@@ -73,13 +89,21 @@ function digest_to_path() {
|
||||
# We can't really test the output for non-JSON output, but we can smoke test it.
|
||||
@test "umoci stat [smoke]" {
|
||||
# Set some values to make sure they show up in stat properly.
|
||||
umoci config --config.user "foobar" --image "${IMAGE}:${TAG}"
|
||||
umoci config --image "${IMAGE}:${TAG}" \
|
||||
--config.user "foobar" \
|
||||
--manifest.annotation "org.opencontainers.umoci.test=foo"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
# Make sure that stat looks about right.
|
||||
umoci stat --image "${IMAGE}:${TAG}"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
# We should have some manifest information.
|
||||
echo "$output" | grep "== MANIFEST =="
|
||||
echo "$output" | grep "Media Type: application/vnd.oci.image.manifest.v1+json"
|
||||
echo "$output" | grep "org.opencontainers.umoci.test: foo"
|
||||
echo "$output" | grep "org.opencontainers.image.ref.name: ${TAG}"
|
||||
|
||||
# We should have some config information.
|
||||
echo "$output" | grep "== CONFIG =="
|
||||
echo "$output" | grep "Media Type: application/vnd.oci.image.config.v1+json"
|
||||
@@ -97,6 +121,21 @@ function digest_to_path() {
|
||||
}
|
||||
|
||||
BLANK_IMAGE_STAT="$(cat <<EOF
|
||||
== MANIFEST ==
|
||||
Schema Version: 2
|
||||
Media Type: application/vnd.oci.image.manifest.v1+json
|
||||
Config:
|
||||
Descriptor:
|
||||
Media Type: application/vnd.oci.image.config.v1+json
|
||||
Digest: sha256:e5101a46118c740a7709af8eaeec19cbc50a567f4fe7741f8420af39a3779a77
|
||||
Size: 135B
|
||||
Descriptor:
|
||||
Media Type: application/vnd.oci.image.manifest.v1+json
|
||||
Digest: sha256:98a4b5d5fe4ea076a0a9059075dad54741e055fd0fa016903a8e2b858dcbad80
|
||||
Size: 249B
|
||||
Annotations:
|
||||
org.opencontainers.image.ref.name: latest
|
||||
|
||||
== CONFIG ==
|
||||
Created: 2025-09-05T13:05:10.12345+10:00
|
||||
Author: ""
|
||||
|
||||
@@ -98,13 +98,14 @@ function teardown() {
|
||||
[ "$status" -eq 0 ]
|
||||
image-verify "${IMAGE}"
|
||||
|
||||
# Compare the stats.
|
||||
# Compare the stats -- aside from the refname annotation, they should be
|
||||
# identical.
|
||||
umoci stat --image "${IMAGE}:${TAG}" --json
|
||||
[ "$status" -eq 0 ]
|
||||
oldOutput="$output"
|
||||
oldOutput="$(jq -rM 'del(.manifest.descriptor.annotations["org.opencontainers.image.ref.name"])' <<<"$output")"
|
||||
umoci stat --image "${IMAGE}:${NEW_TAG}" --json
|
||||
[ "$status" -eq 0 ]
|
||||
newOutput="$output"
|
||||
newOutput="$(jq -rM 'del(.manifest.descriptor.annotations["org.opencontainers.image.ref.name"])' <<<"$output")"
|
||||
|
||||
[[ "$oldOutput" == "$newOutput" ]]
|
||||
|
||||
@@ -190,13 +191,14 @@ function teardown() {
|
||||
[ "$status" -eq 0 ]
|
||||
image-verify "${IMAGE}"
|
||||
|
||||
# Compare the stats.
|
||||
# Compare the stats -- aside from the refname annotation, they should be
|
||||
# identical.
|
||||
umoci stat --image "${IMAGE}:${TAG}" --json
|
||||
[ "$status" -eq 0 ]
|
||||
oldOutput="$output"
|
||||
oldOutput="$(jq -rM 'del(.manifest.descriptor.annotations["org.opencontainers.image.ref.name"])' <<<"$output")"
|
||||
umoci stat --image "${IMAGE}:${NEW_TAG}" --json
|
||||
[ "$status" -eq 0 ]
|
||||
newOutput="$output"
|
||||
newOutput="$(jq -rM 'del(.manifest.descriptor.annotations["org.opencontainers.image.ref.name"])' <<<"$output")"
|
||||
|
||||
[[ "$oldOutput" == "$newOutput" ]]
|
||||
|
||||
|
||||
69
utils.go
69
utils.go
@@ -154,9 +154,7 @@ func ReadBundleMeta(bundle string) (_ Meta, Err error) {
|
||||
// TODO: Implement support for manifest lists, this should also be able to
|
||||
// contain stat information for a list of manifests.
|
||||
type ManifestStat struct {
|
||||
// TODO: Flesh this out. Currently it's only really being used to get an
|
||||
// equivalent of docker-history(1). We really need to add more
|
||||
// information about it.
|
||||
Manifest manifestStat `json:"manifest"`
|
||||
|
||||
// Config stores information about the configuration of a manifest.
|
||||
Config configStat `json:"config"`
|
||||
@@ -294,7 +292,14 @@ func pprintDescriptor(w io.Writer, prefix string, descriptor ispec.Descriptor) e
|
||||
// define their own custom templates for different blocks (meaning that this
|
||||
// should use text/template rather than using tabwriters manually.
|
||||
func (ms ManifestStat) Format(w io.Writer) error {
|
||||
if _, err := fmt.Fprintln(w, "== CONFIG =="); err != nil {
|
||||
if _, err := fmt.Fprintln(w, "== MANIFEST =="); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ms.Manifest.pprint(w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintln(w, "\n== CONFIG =="); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ms.Config.pprint(w); err != nil {
|
||||
@@ -311,6 +316,57 @@ func (ms ManifestStat) Format(w io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// manifestStat contains information about the image manifest.
|
||||
type manifestStat struct {
|
||||
// Descriptor is the descriptor for the configuration JSON.
|
||||
Descriptor ispec.Descriptor `json:"descriptor"`
|
||||
|
||||
// Manifest is the contents of the image manifest.
|
||||
Manifest ispec.Manifest `json:"-"`
|
||||
|
||||
// RawData is the raw data stream of the blob, which is output when we
|
||||
// provide JSON output (to make sure no information is lost in --json
|
||||
// mode).
|
||||
RawData json.RawMessage `json:"blob"`
|
||||
}
|
||||
|
||||
func (m manifestStat) pprint(w io.Writer) error {
|
||||
manifest := m.Manifest
|
||||
if err := pprint(w, "", "Schema Version", strconv.Itoa(manifest.SchemaVersion)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := pprint(w, "", "Media Type", manifest.MediaType); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO(image-spec v1.1): manifest.ArtifactType
|
||||
// TODO(image-spec v1.1): manifest.Subject
|
||||
if err := pprint(w, "", "Config"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := pprintDescriptor(w, "\t", manifest.Config); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(manifest.Layers) > 0 {
|
||||
if err := pprint(w, "", "Layers"); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, layer := range manifest.Layers {
|
||||
if err := pprintDescriptor(w, "\t", layer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(manifest.Annotations) > 0 {
|
||||
if err := pprintMap(w, "", "Annotations", manifest.Annotations); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := pprintDescriptor(w, "", m.Descriptor); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// configStat contains information about the image configuration of this
|
||||
// manifest.
|
||||
type configStat struct {
|
||||
@@ -470,6 +526,11 @@ func Stat(ctx context.Context, engine casext.Engine, manifestDescriptor ispec.De
|
||||
// Should _never_ be reached.
|
||||
return stat, fmt.Errorf("[internal error] unknown manifest blob type: %s", manifestBlob.Descriptor.MediaType)
|
||||
}
|
||||
stat.Manifest = manifestStat{
|
||||
Descriptor: manifestDescriptor,
|
||||
Manifest: manifest,
|
||||
RawData: manifestBlob.RawData,
|
||||
}
|
||||
|
||||
// Now get the config.
|
||||
configBlob, err := engine.FromDescriptor(ctx, manifest.Config)
|
||||
|
||||
Reference in New Issue
Block a user