mirror of
https://github.com/opencontainers/umoci.git
synced 2026-02-05 18:45:08 +01:00
1036 lines
35 KiB
Go
1036 lines
35 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
/*
|
|
* umoci: Umoci Modifies Open Containers' Images
|
|
* Copyright (C) 2016-2025 SUSE LLC
|
|
* Copyright (C) 2026 Aleksa Sarai <cyphar@cyphar.com>
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package umoci
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/opencontainers/go-digest"
|
|
imeta "github.com/opencontainers/image-spec/specs-go"
|
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestPprint(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
key string
|
|
values []string
|
|
expected string
|
|
}{
|
|
{"NoValue", "KeyA", nil, "KeyA:\n"},
|
|
{"OneValue", "KeyB", []string{"foobar"}, "KeyB: foobar\n"},
|
|
{"MultipleValues", "Multiple Values", []string{"a", "b", "c", "d", "e"}, "Multiple Values: a, b, c, d, e\n"},
|
|
{"CommaValue", "Has a comma", []string{"a, b, c, d, e"}, `Has a comma: "a, b, c, d, e"` + "\n"},
|
|
{"CommaValues", "Has commas", []string{"a,b", "c,d,e"}, `Has commas: "a,b", "c,d,e"` + "\n"},
|
|
{"QuotedValue", "Funky value", []string{"foo bar\tba\\z"}, `Funky value: "foo bar\tba\\z"` + "\n"},
|
|
{"QuotedValues", "Funky Values", []string{"a b", "c\nd", "e\t\bf", "foobar-baz"}, `Funky Values: "a b", "c\nd", "e\t\bf", foobar-baz` + "\n"},
|
|
{"WhitespaceValue", "whitespace value", []string{"a b"}, `whitespace value: "a b"` + "\n"},
|
|
{"WhitespaceValues", "multiple whitespace values", []string{"a b", "foo\u00a0bar", "abc\u202fdef"}, `multiple whitespace values: "a b", "foo\u00a0bar", "abc\u202fdef"` + "\n"},
|
|
} {
|
|
for _, prefix := range []struct {
|
|
name, prefix string
|
|
}{
|
|
{"-NoPrefix", ""},
|
|
{"-PrefixTab", "\t"},
|
|
{"-PrefixTabs", "\t\t"},
|
|
{"-PrefixOther", "...\t"},
|
|
} {
|
|
t.Run(test.name+prefix.name, func(t *testing.T) {
|
|
var output bytes.Buffer
|
|
err := pprint(&output, prefix.prefix, test.key, test.values...)
|
|
require.NoErrorf(t, err, "pprint(%q, %q, %v...)", prefix.prefix, test.key, test.values)
|
|
|
|
expected := prefix.prefix + test.expected
|
|
assert.Equal(t, expected, output.String())
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPprintSlice(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
prefix, key string
|
|
data []string
|
|
expected string
|
|
}{
|
|
{"Nil", "\t", "Nil Values", nil, "\tNil Values: (empty)\n"},
|
|
{"Empty", "\t\t", "Empty Values", []string{}, "\t\tEmpty Values: (empty)\n"},
|
|
{"Basic", "\t\t\t", "Basic Values", []string{"a", "b", "cdef"}, "\t\t\t" + `Basic Values:
|
|
a
|
|
b
|
|
cdef
|
|
`},
|
|
{"Whitespace", "\t\t\t", "Whitespace Values", []string{" a b c ", "foo bar\n"}, "\t\t\t" + `Whitespace Values:
|
|
" a b c "
|
|
"foo bar\n"
|
|
`},
|
|
{"Quoted", "\t\t\t", "Quoted Values", []string{"ab", "c\tdef", "foo-bar\n"}, "\t\t\t" + `Quoted Values:
|
|
ab
|
|
"c\tdef"
|
|
"foo-bar\n"
|
|
`},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var output bytes.Buffer
|
|
err := pprintSlice(&output, test.prefix, test.key, test.data)
|
|
require.NoErrorf(t, err, "pprint(%q, %q, %v)", test.prefix, test.key, test.data)
|
|
assert.Equal(t, test.expected, output.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPprintMap(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
prefix, key string
|
|
data map[string]string
|
|
expected string
|
|
}{
|
|
{"Nil", "\t", "Nil Map", nil, "\tNil Map: (empty)\n"},
|
|
{"Empty", "\t\t", "Empty Map", map[string]string{}, "\t\tEmpty Map: (empty)\n"},
|
|
{"Basic", "\t\t\t", "Basic Map", map[string]string{"a": "b", "c.e.f": "foobar123", "01": "2345"}, "\t\t\t" + `Basic Map:
|
|
01: 2345
|
|
a: b
|
|
c.e.f: foobar123
|
|
`},
|
|
{"Whitespace", "\t\t\t", "Whitespace Values", map[string]string{" a b c ": "cdef", "foo": "foo bar\n", "a b": "c d"}, "\t\t\t" + `Whitespace Values:
|
|
" a b c ": cdef
|
|
"a b": "c d"
|
|
foo: "foo bar\n"
|
|
`},
|
|
{"Quoted", "\t\t\t", "Quoted Map", map[string]string{"a\tb": "cdef", "ab": "c\tdef", "foo": "bar\n", "c\tef": "foo\u00a0bar123"}, "\t\t\t" + `Quoted Map:
|
|
"a\tb": cdef
|
|
ab: "c\tdef"
|
|
"c\tef": "foo\u00a0bar123"
|
|
foo: "bar\n"
|
|
`},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var output bytes.Buffer
|
|
err := pprintMap(&output, test.prefix, test.key, test.data)
|
|
require.NoErrorf(t, err, "pprint(%q, %q, %v)", test.prefix, test.key, test.data)
|
|
assert.Equal(t, test.expected, output.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
type set[T comparable] = map[T]struct{}
|
|
|
|
func mkset[T comparable](ks ...T) set[T] {
|
|
set := make(set[T], len(ks))
|
|
for _, k := range ks {
|
|
set[k] = struct{}{}
|
|
}
|
|
return set
|
|
}
|
|
|
|
func TestPprintSet(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
prefix, key string
|
|
data set[string]
|
|
expected string
|
|
}{
|
|
{"Nil", "\t", "Nil Set", nil, "\tNil Set: (empty)\n"},
|
|
{"Empty", "\t\t", "Empty Set", set[string]{}, "\t\tEmpty Set: (empty)\n"},
|
|
{"Basic", "\t\t\t", "Basic Set", mkset("a", "b", "c.e.f", "foobar123", "01", "2345"), "\t\t\tBasic Set: 01, 2345, a, b, c.e.f, foobar123\n"},
|
|
{"Whitespace", "\t\t\t", "Whitespace Set", mkset(" a b c ", "foo bar\n", "c\tef", "foo\u00a0bar123"), "\t\t\t" + `Whitespace Set: " a b c ", "c\tef", "foo bar\n", "foo\u00a0bar123"` + "\n"},
|
|
{"Quoted", "\t\t\t", "Quoted Set", mkset("a\tb", "cdef", "foo", "bar\n", "c\tef", "foo\u00a0bar123"), "\t\t\t" + `Quoted Set: "a\tb", "bar\n", "c\tef", cdef, foo, "foo\u00a0bar123"` + "\n"},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var output bytes.Buffer
|
|
err := pprintSet(&output, test.prefix, test.key, test.data)
|
|
require.NoErrorf(t, err, "pprint(%q, %q, %v)", test.prefix, test.key, test.data)
|
|
assert.Equal(t, test.expected, output.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPprintPlatform(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
prefix string
|
|
platform ispec.Platform
|
|
expected string
|
|
}{
|
|
{"Basic", "\t\t", ispec.Platform{
|
|
OS: "linux",
|
|
Architecture: "amd64",
|
|
}, "\t\t" + `Platform:
|
|
OS: linux
|
|
Architecture: amd64
|
|
`},
|
|
{"ArchVariant", "\t\t", ispec.Platform{
|
|
OS: "freebsd",
|
|
Architecture: "arm64",
|
|
Variant: "v7",
|
|
}, "\t\t" + `Platform:
|
|
OS: freebsd
|
|
Architecture: arm64 (v7)
|
|
`},
|
|
{"OSFeatures", "\t\t", ispec.Platform{
|
|
OS: "windows",
|
|
OSVersion: "10.0.14393.1066",
|
|
OSFeatures: []string{"win32k", "win-dummy"},
|
|
Architecture: "amd64",
|
|
}, "\t\t" + `Platform:
|
|
OS: windows
|
|
OS Version: 10.0.14393.1066
|
|
OS Features: win32k, win-dummy
|
|
Architecture: amd64
|
|
`},
|
|
{"Whitespace", "\t\t", ispec.Platform{
|
|
OS: "fake os",
|
|
OSVersion: "a b c",
|
|
OSFeatures: []string{"a,b", "c d e f"},
|
|
Architecture: "arm is cool",
|
|
Variant: "version 7",
|
|
}, "\t\t" + `Platform:
|
|
OS: "fake os"
|
|
OS Version: "a b c"
|
|
OS Features: "a,b", "c d e f"
|
|
Architecture: "arm is cool" (version 7)
|
|
`},
|
|
{"Quoted", "\t\t", ispec.Platform{
|
|
OS: "fake\tos\n",
|
|
OSVersion: "a\vbc",
|
|
OSFeatures: []string{"a\tb", "c\u00a0bar"},
|
|
Architecture: "arm\nis\ncool",
|
|
Variant: "version\b7",
|
|
}, "\t\t" + `Platform:
|
|
OS: "fake\tos\n"
|
|
OS Version: "a\vbc"
|
|
OS Features: "a\tb", "c\u00a0bar"
|
|
Architecture: "arm\nis\ncool" ("version\b7")
|
|
`},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var output bytes.Buffer
|
|
err := pprintPlatform(&output, test.prefix, test.platform)
|
|
require.NoErrorf(t, err, "pprint(%q, %#v)", test.prefix, test.platform)
|
|
assert.Equal(t, test.expected, output.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPprintDescriptor(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
prefix string
|
|
descriptor ispec.Descriptor
|
|
expected string
|
|
}{
|
|
{"Empty", "\t\t", ispec.Descriptor{}, "\t\t" + `Descriptor:
|
|
Media Type: ""
|
|
Digest: ""
|
|
Size: 0B
|
|
`},
|
|
{"EmptyJSON", "\t\t", ispec.DescriptorEmptyJSON, "\t\t" + `Descriptor:
|
|
Media Type: application/vnd.oci.empty.v1+json
|
|
Digest: sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
|
|
Size: 2B
|
|
Data: (2 bytes)
|
|
0000: 7b7d {}
|
|
`},
|
|
// Adapted from <https://github.com/opencontainers/image-spec/blob/v1.1.1/descriptor.md#examples>.
|
|
{"SpecExample-1", "\t\t", ispec.Descriptor{
|
|
MediaType: ispec.MediaTypeImageManifest,
|
|
Digest: "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
|
|
Size: 7682,
|
|
}, "\t\t" + `Descriptor:
|
|
Media Type: application/vnd.oci.image.manifest.v1+json
|
|
Digest: sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270
|
|
Size: 7.682kB
|
|
`},
|
|
{"SpecExample-2", "\t\t", ispec.Descriptor{
|
|
MediaType: ispec.MediaTypeImageManifest,
|
|
Digest: "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
|
|
Size: 7682,
|
|
URLs: []string{
|
|
"https://example.com/example-manifest",
|
|
},
|
|
}, "\t\t" + `Descriptor:
|
|
Media Type: application/vnd.oci.image.manifest.v1+json
|
|
Digest: sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270
|
|
Size: 7.682kB
|
|
URLs:
|
|
https://example.com/example-manifest
|
|
`},
|
|
{"SpecExample-3", "\t\t", ispec.Descriptor{
|
|
MediaType: "",
|
|
ArtifactType: "application/vnd.example.sbom.v1",
|
|
Digest: "sha256:87923725d74f4bfb94c9e86d64170f7521aad8221a5de834851470ca142da630",
|
|
Size: 123,
|
|
}, "\t\t" + `Descriptor:
|
|
Media Type: ""
|
|
Artifact Type: application/vnd.example.sbom.v1
|
|
Digest: sha256:87923725d74f4bfb94c9e86d64170f7521aad8221a5de834851470ca142da630
|
|
Size: 123B
|
|
`},
|
|
{"Basic", "\t\t", ispec.Descriptor{
|
|
MediaType: "image/png",
|
|
Digest: "sha256:7d30ef97fe167eb78d5c52503096522ebba7ba95789ba735fd68da9d4838f84d",
|
|
Size: 61235,
|
|
}, "\t\t" + `Descriptor:
|
|
Media Type: image/png
|
|
Digest: sha256:7d30ef97fe167eb78d5c52503096522ebba7ba95789ba735fd68da9d4838f84d
|
|
Size: 61.23kB
|
|
`},
|
|
{"ArtifactType", "\t\t", ispec.Descriptor{
|
|
MediaType: ispec.MediaTypeEmptyJSON,
|
|
ArtifactType: "image/png",
|
|
Digest: "sha256:7a30ef97fe167eb78d5c52503096522ebba7ba95789ba735fd68da9d4838f84d",
|
|
Size: 82235,
|
|
}, "\t\t" + `Descriptor:
|
|
Media Type: application/vnd.oci.empty.v1+json
|
|
Artifact Type: image/png
|
|
Digest: sha256:7a30ef97fe167eb78d5c52503096522ebba7ba95789ba735fd68da9d4838f84d
|
|
Size: 82.23kB
|
|
`},
|
|
{"Annotations", "\t\t", ispec.Descriptor{
|
|
MediaType: ispec.MediaTypeImageManifest,
|
|
Digest: "blake2:acbe05bb922855b8b2fea560e11ad2d135505a9f194a0fa99f1c57d3974990f6853ce22e97273d8dbf7424c09fe43e53ce4080dcc543482be46965bb24639cbe",
|
|
Size: 59129319,
|
|
Annotations: map[string]string{
|
|
"com.example.foo": "abc123",
|
|
"ci.umo.foobar": "test",
|
|
"com.cyphar.test": "https://www.cyphar.com/",
|
|
},
|
|
}, "\t\t" + `Descriptor:
|
|
Media Type: application/vnd.oci.image.manifest.v1+json
|
|
Digest: blake2:acbe05bb922855b8b2fea560e11ad2d135505a9f194a0fa99f1c57d3974990f6853ce22e97273d8dbf7424c09fe43e53ce4080dcc543482be46965bb24639cbe
|
|
Size: 59.13MB
|
|
Annotations:
|
|
ci.umo.foobar: test
|
|
com.cyphar.test: https://www.cyphar.com/
|
|
com.example.foo: abc123
|
|
`},
|
|
{"URLs", "\t\t", ispec.Descriptor{
|
|
MediaType: ispec.MediaTypeImageLayerGzip,
|
|
Digest: "sha512:dd65b6a8d277c7b06003178fc7a125fa6ad021dff2beffd82df0b469f2afe735312e2932608d42d1bc8421141edb0882680e36d45f5d804783ef555a3566a9f8",
|
|
Size: 926812731,
|
|
URLs: []string{
|
|
"https://www.example.com/example.txt",
|
|
"https://www.cyphar.com/",
|
|
},
|
|
}, "\t\t" + `Descriptor:
|
|
Media Type: application/vnd.oci.image.layer.v1.tar+gzip
|
|
Digest: sha512:dd65b6a8d277c7b06003178fc7a125fa6ad021dff2beffd82df0b469f2afe735312e2932608d42d1bc8421141edb0882680e36d45f5d804783ef555a3566a9f8
|
|
Size: 926.8MB
|
|
URLs:
|
|
https://www.example.com/example.txt
|
|
https://www.cyphar.com/
|
|
`},
|
|
{"Data-Short", "\t\t", ispec.Descriptor{
|
|
MediaType: "application/text",
|
|
Digest: "sha256:aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f",
|
|
Size: 7,
|
|
Data: []byte("foobar\n"),
|
|
}, "\t\t" + `Descriptor:
|
|
Media Type: application/text
|
|
Digest: sha256:aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f
|
|
Size: 7B
|
|
Data: (7 bytes)
|
|
0000: 666f 6f62 6172 0a foobar.
|
|
`},
|
|
{"Data-ShortMultiline", "\t\t", ispec.Descriptor{
|
|
MediaType: "application/text",
|
|
Digest: "sha256:b47cc0f104b62d4c7c30bcd68fd8e67613e287dc4ad8c310ef10cbadea9c4380",
|
|
Size: 45,
|
|
Data: []byte("The quick brown fox jumps over the lazy dog.\n"),
|
|
}, "\t\t" + `Descriptor:
|
|
Media Type: application/text
|
|
Digest: sha256:b47cc0f104b62d4c7c30bcd68fd8e67613e287dc4ad8c310ef10cbadea9c4380
|
|
Size: 45B
|
|
Data: (45 bytes)
|
|
0000: 5468 6520 7175 6963 6b20 6272 6f77 6e20 The quick brown ` + `
|
|
0010: 666f 7820 6a75 6d70 7320 6f76 6572 2074 fox jumps over t
|
|
0020: 6865 206c 617a 7920 646f 672e 0a he lazy dog..
|
|
`},
|
|
{"Data-Long", "\t\t", ispec.Descriptor{
|
|
MediaType: "legal/x-license; Apache-2.0",
|
|
Digest: "sha256:af38b8d8986cfc6f3132e289f5e6abf0facdf72f1f67c0f87fa8db3e2e8a9c55",
|
|
Size: 524,
|
|
Data: []byte(`
|
|
Apache License
|
|
Version 2.0, January 2004
|
|
http://www.apache.org/licenses/
|
|
|
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
|
|
1. Definitions.
|
|
|
|
"License" shall mean the terms and conditions for use, reproduction,
|
|
and distribution as defined by Sections 1 through 9 of this document.
|
|
|
|
"Licensor" shall mean the copyright owner or entity authorized by
|
|
the copyright owner that is granting the License.
|
|
`),
|
|
}, "\t\t" + `Descriptor:
|
|
Media Type: "legal/x-license; Apache-2.0"
|
|
Digest: sha256:af38b8d8986cfc6f3132e289f5e6abf0facdf72f1f67c0f87fa8db3e2e8a9c55
|
|
Size: 524B
|
|
Data: (524 bytes)
|
|
0000: 0a20 2020 2020 2020 2020 2020 2020 2020 . ` + `
|
|
0010: 2020 2020 2020 2020 2020 2020 2020 2020 ` + `
|
|
0020: 2020 4170 6163 6865 204c 6963 656e 7365 Apache License` + `
|
|
0030: 0a20 2020 2020 2020 2020 2020 2020 2020 . ` + `
|
|
0040: 2020 2020 2020 2020 2020 2020 5665 7273 Vers` + `
|
|
0050: 696f 6e20 322e 302c 204a 616e 7561 7279 ion 2.0, January` + `
|
|
0060: 2032 3030 340a 2020 2020 2020 2020 2020 2004. ` + `
|
|
0070: 2020 2020 2020 2020 2020 2020 2020 6874 ht` + `
|
|
0080: 7470 3a2f 2f77 7777 2e61 7061 6368 652e tp://www.apache.` + `
|
|
0090: 6f72 672f 6c69 6365 6e73 6573 2f0a 0a20 org/licenses/.. ` + `
|
|
00a0: 2020 5445 524d 5320 414e 4420 434f 4e44 TERMS AND COND` + `
|
|
00b0: 4954 494f 4e53 2046 4f52 2055 5345 2c20 ITIONS FOR USE, ` + `
|
|
00c0: 5245 5052 4f44 5543 5449 4f4e 2c20 414e REPRODUCTION, AN` + `
|
|
00d0: 4420 4449 5354 5249 4255 5449 4f4e 0a0a D DISTRIBUTION..` + `
|
|
00e0: 2020 2031 2e20 4465 6669 6e69 7469 6f6e 1. Definition` + `
|
|
00f0: 732e 0a0a 2020 2020 2020 224c 6963 656e s... "Licen` + `
|
|
.... (extra 268 bytes omitted)
|
|
`},
|
|
{"Whitespace", "\t\t", ispec.Descriptor{
|
|
MediaType: "application/x-dummy; foo",
|
|
Digest: "dummy: a b c d",
|
|
Size: 123,
|
|
URLs: []string{
|
|
"https:// www. example.com/example.txt",
|
|
"https:// www. cyphar.com/",
|
|
},
|
|
Annotations: map[string]string{
|
|
"com. example.foo": "abc123",
|
|
"ci.umo.foobar": "test foo",
|
|
"com cyphar test": "https www cyphar com ",
|
|
},
|
|
}, "\t\t" + `Descriptor:
|
|
Media Type: "application/x-dummy; foo"
|
|
Digest: "dummy: a b c d"
|
|
Size: 123B
|
|
URLs:
|
|
"https:// www. example.com/example.txt"
|
|
"https:// www. cyphar.com/"
|
|
Annotations:
|
|
ci.umo.foobar: "test foo"
|
|
"com cyphar test": "https www cyphar com "
|
|
"com. example.foo": abc123
|
|
`},
|
|
{"Quoted", "\t\t", ispec.Descriptor{
|
|
MediaType: "application/x-dummy;\nfoo",
|
|
Digest: "dummy:a\bb\bc\td",
|
|
Size: 123,
|
|
URLs: []string{
|
|
"\t\n",
|
|
},
|
|
Annotations: map[string]string{
|
|
"com.\texample.foo": "abc123",
|
|
"ci.umo.foobar": "test\nfoo",
|
|
"com cyphar test": "https \t www cyphar com ",
|
|
},
|
|
}, "\t\t" + `Descriptor:
|
|
Media Type: "application/x-dummy;\nfoo"
|
|
Digest: "dummy:a\bb\bc\td"
|
|
Size: 123B
|
|
URLs:
|
|
"\t\n"
|
|
Annotations:
|
|
ci.umo.foobar: "test\nfoo"
|
|
"com cyphar test": "https \t www cyphar com "
|
|
"com.\texample.foo": abc123
|
|
`},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var output bytes.Buffer
|
|
err := pprintDescriptor(&output, test.prefix, test.descriptor)
|
|
require.NoErrorf(t, err, "pprint(%q, %#v)", test.prefix, test.descriptor)
|
|
assert.Equal(t, test.expected, output.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPprintImageConfig(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
prefix string
|
|
config ispec.ImageConfig
|
|
expected string
|
|
}{
|
|
{"Empty", "\t\t", ispec.ImageConfig{}, "\t\t" + `Image Config:
|
|
User: ""
|
|
Command: (empty)
|
|
`},
|
|
{"User", "\t\t", ispec.ImageConfig{
|
|
User: "foo:bar",
|
|
}, "\t\t" + `Image Config:
|
|
User: foo:bar
|
|
Command: (empty)
|
|
`},
|
|
{"Command", "\t\t", ispec.ImageConfig{
|
|
User: "0",
|
|
Cmd: []string{"/bin/bash", "-c", "while true; sleep 1s; done;"},
|
|
}, "\t\t" + `Image Config:
|
|
User: 0
|
|
Command:
|
|
/bin/bash
|
|
-c
|
|
"while true; sleep 1s; done;"
|
|
`},
|
|
{"Entrypoint", "\t\t", ispec.ImageConfig{
|
|
User: "0",
|
|
Entrypoint: []string{"/bin/bash", "-c"},
|
|
}, "\t\t" + `Image Config:
|
|
User: 0
|
|
Entrypoint:
|
|
/bin/bash
|
|
-c
|
|
Command: (empty)
|
|
`},
|
|
{"Entrypoint+Command", "\t\t", ispec.ImageConfig{
|
|
User: "0",
|
|
Entrypoint: []string{"/bin/bash", "-c"},
|
|
Cmd: []string{"while true; sleep 1s; done;"},
|
|
}, "\t\t" + `Image Config:
|
|
User: 0
|
|
Entrypoint:
|
|
/bin/bash
|
|
-c
|
|
Command:
|
|
"while true; sleep 1s; done;"
|
|
`},
|
|
{"Command+Entrypoint-Escaped", "\t\t", ispec.ImageConfig{
|
|
User: "0",
|
|
ArgsEscaped: true,
|
|
Entrypoint: []string{"/bin/bash -c "},
|
|
Cmd: []string{"while true; sleep 1s; done;"},
|
|
}, "\t\t" + `Image Config:
|
|
User: 0
|
|
Entrypoint (escaped):
|
|
"/bin/bash -c "
|
|
Command (escaped):
|
|
"while true; sleep 1s; done;"
|
|
`},
|
|
{"Environment", "\t\t", ispec.ImageConfig{
|
|
Env: []string{
|
|
"PATH=/bin:/usr/bin",
|
|
"HOME=/home/cyphar",
|
|
"FOOBAR=The quick brown fox jumps over the lazy dog.",
|
|
"EQUAL==",
|
|
},
|
|
}, "\t\t" + `Image Config:
|
|
User: ""
|
|
Command: (empty)
|
|
Environment:
|
|
PATH=/bin:/usr/bin
|
|
HOME=/home/cyphar
|
|
"FOOBAR=The quick brown fox jumps over the lazy dog."
|
|
EQUAL==
|
|
`},
|
|
{"StopSignal", "\t\t", ispec.ImageConfig{
|
|
StopSignal: "SIGKILL",
|
|
}, "\t\t" + `Image Config:
|
|
User: ""
|
|
Command: (empty)
|
|
Stop Signal: SIGKILL
|
|
`},
|
|
{"ExposedPorts", "\t\t", ispec.ImageConfig{
|
|
ExposedPorts: mkset("80/tcp", "50000/udp"),
|
|
}, "\t\t" + `Image Config:
|
|
User: ""
|
|
Command: (empty)
|
|
Exposed Ports: 50000/udp, 80/tcp
|
|
`},
|
|
{"Volumes", "\t\t", ispec.ImageConfig{
|
|
Volumes: mkset("/foo/bar", "/baz", "/beep boop"),
|
|
}, "\t\t" + `Image Config:
|
|
User: ""
|
|
Command: (empty)
|
|
Volumes: /baz, "/beep boop", /foo/bar
|
|
`},
|
|
{"Labels", "\t\t", ispec.ImageConfig{
|
|
Labels: map[string]string{
|
|
"foo bar": "baz",
|
|
},
|
|
}, "\t\t" + `Image Config:
|
|
User: ""
|
|
Command: (empty)
|
|
Labels:
|
|
"foo bar": baz
|
|
`},
|
|
{"Basic", "\t\t", ispec.ImageConfig{
|
|
Cmd: []string{"bash"},
|
|
WorkingDir: "/go",
|
|
Env: []string{
|
|
"PATH=/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
"GOLANG_VERSION=1.25.1",
|
|
"GOTOOLCHAIN=local",
|
|
"GOPATH=/go",
|
|
},
|
|
}, "\t\t" + `Image Config:
|
|
User: ""
|
|
Command:
|
|
bash
|
|
Working Directory: /go
|
|
Environment:
|
|
PATH=/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
GOLANG_VERSION=1.25.1
|
|
GOTOOLCHAIN=local
|
|
GOPATH=/go
|
|
`},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var output bytes.Buffer
|
|
err := pprintImageConfig(&output, test.prefix, test.config)
|
|
require.NoErrorf(t, err, "pprint(%q, %#v)", test.prefix, test.config)
|
|
assert.Equal(t, test.expected, output.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func mktime(sec, nsec int64) *time.Time {
|
|
time := time.Unix(sec, nsec).UTC()
|
|
return &time
|
|
}
|
|
|
|
func TestPprintImage(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
prefix string
|
|
image ispec.Image
|
|
expected string
|
|
}{
|
|
{"Empty", "\t\t", ispec.Image{}, "\t\t" + `Author: ""
|
|
Platform:
|
|
OS: ""
|
|
Architecture: ""
|
|
Image Config:
|
|
User: ""
|
|
Command: (empty)
|
|
`},
|
|
// Adapted from <https://github.com/opencontainers/image-spec/blob/v1.1.1/config.md#example>.
|
|
{"SpecExample", "\t\t", ispec.Image{
|
|
Created: mktime(1446330176, 15925234),
|
|
Author: "Alyssa P. Hacker <alyspdev@example.com>",
|
|
Platform: ispec.Platform{
|
|
OS: "linux",
|
|
Architecture: "amd64",
|
|
},
|
|
Config: ispec.ImageConfig{
|
|
User: "alice",
|
|
Entrypoint: []string{"/bin/my-app-binary"},
|
|
Cmd: []string{"--foreground", "--config", "/etc/my-app.d/default.cfg"},
|
|
WorkingDir: "/home/alice",
|
|
Env: []string{
|
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
"FOO=oci_is_a",
|
|
"BAR=well_written_spec",
|
|
},
|
|
ExposedPorts: mkset("8080/tcp"),
|
|
Volumes: mkset("/var/job-result-data", "/var/log/my-app-logs"),
|
|
Labels: map[string]string{
|
|
"com.example.project.git.url": "https://example.com/project.git",
|
|
"com.example.project.git.commit": "45a939b2999782a3f005621a8d0f29aa387e1d6b",
|
|
},
|
|
},
|
|
// Not printed by pprintImage, as this is handled by the history
|
|
// pretty-printer.
|
|
RootFS: ispec.RootFS{
|
|
Type: "layers",
|
|
DiffIDs: []digest.Digest{
|
|
"sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1",
|
|
"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef",
|
|
},
|
|
},
|
|
History: []ispec.History{
|
|
{
|
|
Created: mktime(1446330174, 690851953),
|
|
CreatedBy: "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /",
|
|
},
|
|
{
|
|
Created: mktime(1446330175, 613815829),
|
|
CreatedBy: "/bin/sh -c #(nop) CMD [\"sh\"]",
|
|
EmptyLayer: true,
|
|
},
|
|
{
|
|
Created: mktime(1446330176, 329850019),
|
|
CreatedBy: "/bin/sh -c apk add curl",
|
|
},
|
|
},
|
|
}, "\t\t" + `Created: 2015-10-31T22:22:56.015925234Z
|
|
Author: "Alyssa P. Hacker <alyspdev@example.com>"
|
|
Platform:
|
|
OS: linux
|
|
Architecture: amd64
|
|
Image Config:
|
|
User: alice
|
|
Entrypoint:
|
|
/bin/my-app-binary
|
|
Command:
|
|
--foreground
|
|
--config
|
|
/etc/my-app.d/default.cfg
|
|
Working Directory: /home/alice
|
|
Environment:
|
|
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
FOO=oci_is_a
|
|
BAR=well_written_spec
|
|
Exposed Ports: 8080/tcp
|
|
Volumes: /var/job-result-data, /var/log/my-app-logs
|
|
Labels:
|
|
com.example.project.git.commit: 45a939b2999782a3f005621a8d0f29aa387e1d6b
|
|
com.example.project.git.url: https://example.com/project.git
|
|
`},
|
|
{"Basic", "\t\t", ispec.Image{
|
|
Created: mktime(1757077500, 0),
|
|
Author: "Aleksa Sarai <cyphar@cyphar.com>",
|
|
Platform: ispec.Platform{
|
|
OS: "linux",
|
|
Architecture: "amd64",
|
|
},
|
|
Config: ispec.ImageConfig{
|
|
User: "root",
|
|
Entrypoint: []string{"/bin/bash", "-c"},
|
|
Cmd: []string{"shutdown -h"},
|
|
WorkingDir: "/tmp",
|
|
Env: []string{"PATH=/bin:/usr/bin", "HOME=/"},
|
|
StopSignal: "SIGSTOP",
|
|
ExposedPorts: mkset("12345/tcp"),
|
|
Volumes: mkset("/tmp", "/opt/foo/bar"),
|
|
Labels: map[string]string{
|
|
"org.opencontainers.image.url": "https://www.cyphar.com/",
|
|
"org.label-schema.descripton": "foo bar baz",
|
|
},
|
|
},
|
|
}, "\t\t" + `Created: 2025-09-05T13:05:00Z
|
|
Author: "Aleksa Sarai <cyphar@cyphar.com>"
|
|
Platform:
|
|
OS: linux
|
|
Architecture: amd64
|
|
Image Config:
|
|
User: root
|
|
Entrypoint:
|
|
/bin/bash
|
|
-c
|
|
Command:
|
|
"shutdown -h"
|
|
Working Directory: /tmp
|
|
Environment:
|
|
PATH=/bin:/usr/bin
|
|
HOME=/
|
|
Stop Signal: SIGSTOP
|
|
Exposed Ports: 12345/tcp
|
|
Volumes: /opt/foo/bar, /tmp
|
|
Labels:
|
|
org.label-schema.descripton: "foo bar baz"
|
|
org.opencontainers.image.url: https://www.cyphar.com/
|
|
`},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var output bytes.Buffer
|
|
err := pprintImage(&output, test.prefix, test.image)
|
|
require.NoErrorf(t, err, "pprint(%q, %#v)", test.prefix, test.image)
|
|
assert.Equal(t, test.expected, output.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPprintManifest(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
prefix string
|
|
manifest ispec.Manifest
|
|
expected string
|
|
}{
|
|
{"Empty", "\t\t", ispec.Manifest{}, "\t\t" + `Schema Version: 0
|
|
Media Type: ""
|
|
Config:
|
|
Descriptor:
|
|
Media Type: ""
|
|
Digest: ""
|
|
Size: 0B
|
|
`},
|
|
// Adapted from <https://github.com/opencontainers/image-spec/blob/v1.1.1/manifest.md#example-image-manifest>.
|
|
{"SpecExample", "\t\t\t", ispec.Manifest{
|
|
Versioned: imeta.Versioned{SchemaVersion: 2},
|
|
MediaType: ispec.MediaTypeImageManifest,
|
|
Config: ispec.Descriptor{
|
|
MediaType: ispec.MediaTypeImageConfig,
|
|
Digest: "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7",
|
|
Size: 7023,
|
|
},
|
|
Layers: []ispec.Descriptor{
|
|
{
|
|
MediaType: ispec.MediaTypeImageLayerGzip,
|
|
Digest: "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0",
|
|
Size: 32654,
|
|
},
|
|
{
|
|
MediaType: ispec.MediaTypeImageLayerGzip,
|
|
Digest: "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b",
|
|
Size: 16724,
|
|
},
|
|
{
|
|
MediaType: ispec.MediaTypeImageLayerGzip,
|
|
Digest: "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736",
|
|
Size: 73109,
|
|
},
|
|
},
|
|
Subject: &ispec.Descriptor{
|
|
MediaType: ispec.MediaTypeImageManifest,
|
|
Digest: "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
|
|
Size: 7682,
|
|
},
|
|
Annotations: map[string]string{
|
|
"com.example.key1": "value1",
|
|
"com.example.key2": "value2",
|
|
},
|
|
}, "\t\t\t" + `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:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7
|
|
Size: 7.023kB
|
|
Layers:
|
|
Descriptor:
|
|
Media Type: application/vnd.oci.image.layer.v1.tar+gzip
|
|
Digest: sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0
|
|
Size: 32.65kB
|
|
Descriptor:
|
|
Media Type: application/vnd.oci.image.layer.v1.tar+gzip
|
|
Digest: sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b
|
|
Size: 16.72kB
|
|
Descriptor:
|
|
Media Type: application/vnd.oci.image.layer.v1.tar+gzip
|
|
Digest: sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736
|
|
Size: 73.11kB
|
|
Subject:
|
|
Descriptor:
|
|
Media Type: application/vnd.oci.image.manifest.v1+json
|
|
Digest: sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270
|
|
Size: 7.682kB
|
|
Annotations:
|
|
com.example.key1: value1
|
|
com.example.key2: value2
|
|
`},
|
|
// Adapted from <https://github.com/opencontainers/image-spec/blob/v1.1.1/manifest.md#guidelines-for-artifact-usage>.
|
|
{"ArtifactExample-1", "\t\t\t", ispec.Manifest{
|
|
Versioned: imeta.Versioned{SchemaVersion: 2},
|
|
MediaType: ispec.MediaTypeImageManifest,
|
|
ArtifactType: "application/vnd.example+type",
|
|
Config: ispec.DescriptorEmptyJSON,
|
|
Layers: []ispec.Descriptor{ispec.DescriptorEmptyJSON},
|
|
Annotations: map[string]string{
|
|
"oci.opencontainers.image.created": "2023-01-02T03:04:05Z",
|
|
"com.example.data": "payload",
|
|
},
|
|
}, "\t\t\t" + `Schema Version: 2
|
|
Media Type: application/vnd.oci.image.manifest.v1+json
|
|
Artifact Type: application/vnd.example+type
|
|
Config:
|
|
Descriptor:
|
|
Media Type: application/vnd.oci.empty.v1+json
|
|
Digest: sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
|
|
Size: 2B
|
|
Data: (2 bytes)
|
|
0000: 7b7d {}
|
|
Layers:
|
|
Descriptor:
|
|
Media Type: application/vnd.oci.empty.v1+json
|
|
Digest: sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
|
|
Size: 2B
|
|
Data: (2 bytes)
|
|
0000: 7b7d {}
|
|
Annotations:
|
|
com.example.data: payload
|
|
oci.opencontainers.image.created: 2023-01-02T03:04:05Z
|
|
`},
|
|
{"ArtifactExample-2", "\t\t\t", ispec.Manifest{
|
|
Versioned: imeta.Versioned{SchemaVersion: 2},
|
|
MediaType: ispec.MediaTypeImageManifest,
|
|
ArtifactType: "application/vnd.example+type",
|
|
Config: ispec.DescriptorEmptyJSON,
|
|
Layers: []ispec.Descriptor{
|
|
{
|
|
MediaType: "application/vnd.example+type",
|
|
Digest: "sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317",
|
|
Size: 1234,
|
|
},
|
|
},
|
|
}, "\t\t\t" + `Schema Version: 2
|
|
Media Type: application/vnd.oci.image.manifest.v1+json
|
|
Artifact Type: application/vnd.example+type
|
|
Config:
|
|
Descriptor:
|
|
Media Type: application/vnd.oci.empty.v1+json
|
|
Digest: sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
|
|
Size: 2B
|
|
Data: (2 bytes)
|
|
0000: 7b7d {}
|
|
Layers:
|
|
Descriptor:
|
|
Media Type: application/vnd.example+type
|
|
Digest: sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317
|
|
Size: 1.234kB
|
|
`},
|
|
{"ArtifactExample-3", "\t\t\t", ispec.Manifest{
|
|
Versioned: imeta.Versioned{SchemaVersion: 2},
|
|
MediaType: ispec.MediaTypeImageManifest,
|
|
ArtifactType: "application/vnd.example+type",
|
|
Config: ispec.Descriptor{
|
|
MediaType: "application/vnd.example.config.v1+json",
|
|
Digest: "sha256:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03",
|
|
Size: 123,
|
|
},
|
|
Layers: []ispec.Descriptor{
|
|
{
|
|
MediaType: "application/vnd.example+type",
|
|
Digest: "sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317",
|
|
Size: 1234,
|
|
},
|
|
},
|
|
}, "\t\t\t" + `Schema Version: 2
|
|
Media Type: application/vnd.oci.image.manifest.v1+json
|
|
Artifact Type: application/vnd.example+type
|
|
Config:
|
|
Descriptor:
|
|
Media Type: application/vnd.example.config.v1+json
|
|
Digest: sha256:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03
|
|
Size: 123B
|
|
Layers:
|
|
Descriptor:
|
|
Media Type: application/vnd.example+type
|
|
Digest: sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317
|
|
Size: 1.234kB
|
|
`},
|
|
{"Basic", "\t\t", ispec.Manifest{
|
|
Versioned: imeta.Versioned{SchemaVersion: 2},
|
|
MediaType: ispec.MediaTypeImageManifest,
|
|
Config: ispec.Descriptor{
|
|
MediaType: ispec.MediaTypeImageConfig,
|
|
Digest: "sha256:6d79abd4c96299aa91f5a4a46551042407568a3858b00ab460f4ba430984f62c",
|
|
Size: 2297,
|
|
},
|
|
Layers: []ispec.Descriptor{
|
|
{
|
|
MediaType: ispec.MediaTypeImageLayerGzip,
|
|
Digest: "sha256:15b1d8a5ff03aeb0f14c8d39a60a73ef22f656550bfa1bb90d1850f25a0ac0fa",
|
|
Size: 49279531,
|
|
},
|
|
{
|
|
MediaType: ispec.MediaTypeImageLayerGzip,
|
|
Digest: "sha256:22718812f617084a0c5e539e02723b75bf79ea2a589430f820efcbb07f45b91b",
|
|
Size: 25613635,
|
|
},
|
|
{
|
|
MediaType: ispec.MediaTypeImageLayerGzip,
|
|
Digest: "sha256:401a98f7495bee3e8e6943da9f52f0ab1043c43eb1d107a3817fc2a4b916be97",
|
|
Size: 67776756,
|
|
},
|
|
{
|
|
MediaType: ispec.MediaTypeImageLayerGzip,
|
|
Digest: "sha256:1c315634bf9079ab45072808dc1101241352a3762e0bb1ec75369a0f65672ab0",
|
|
Size: 102071864,
|
|
},
|
|
{
|
|
MediaType: ispec.MediaTypeImageLayerGzip,
|
|
Digest: "sha256:330457f0054be4c07fbaeac90483ac4f534113ccd944fe16beb9e079a3ab3a36",
|
|
Size: 60045609,
|
|
},
|
|
{
|
|
MediaType: ispec.MediaTypeImageLayerGzip,
|
|
Digest: "sha256:771ee02be966963d69210ed8243baa8f322661858bae69d1c2d5d13fe4dc92ba",
|
|
Size: 126,
|
|
},
|
|
{
|
|
MediaType: ispec.MediaTypeImageLayerGzip,
|
|
Digest: "sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1",
|
|
Size: 32,
|
|
},
|
|
},
|
|
Annotations: map[string]string{
|
|
"com.docker.official-images.bashbrew.arch": "amd64",
|
|
"org.opencontainers.image.base.digest": "sha256:6f9fd607b2e260d41c51207fb74010ba06931047760494f5f6d59854e7d55db4",
|
|
"org.opencontainers.image.base.name": "buildpack-deps:trixie-scm",
|
|
"org.opencontainers.image.created": "2025-09-03T18:13:04Z",
|
|
"org.opencontainers.image.revision": "be5c27da377afdeebf0c5747560bedd7f96ccb1c",
|
|
"org.opencontainers.image.source": "https://github.com/docker-library/golang.git#be5c27da377afdeebf0c5747560bedd7f96ccb1c:1.25/trixie",
|
|
"org.opencontainers.image.url": "https://hub.docker.com/_/golang",
|
|
"org.opencontainers.image.version": "1.25.1-trixie",
|
|
},
|
|
}, "\t\t" + `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:6d79abd4c96299aa91f5a4a46551042407568a3858b00ab460f4ba430984f62c
|
|
Size: 2.297kB
|
|
Layers:
|
|
Descriptor:
|
|
Media Type: application/vnd.oci.image.layer.v1.tar+gzip
|
|
Digest: sha256:15b1d8a5ff03aeb0f14c8d39a60a73ef22f656550bfa1bb90d1850f25a0ac0fa
|
|
Size: 49.28MB
|
|
Descriptor:
|
|
Media Type: application/vnd.oci.image.layer.v1.tar+gzip
|
|
Digest: sha256:22718812f617084a0c5e539e02723b75bf79ea2a589430f820efcbb07f45b91b
|
|
Size: 25.61MB
|
|
Descriptor:
|
|
Media Type: application/vnd.oci.image.layer.v1.tar+gzip
|
|
Digest: sha256:401a98f7495bee3e8e6943da9f52f0ab1043c43eb1d107a3817fc2a4b916be97
|
|
Size: 67.78MB
|
|
Descriptor:
|
|
Media Type: application/vnd.oci.image.layer.v1.tar+gzip
|
|
Digest: sha256:1c315634bf9079ab45072808dc1101241352a3762e0bb1ec75369a0f65672ab0
|
|
Size: 102.1MB
|
|
Descriptor:
|
|
Media Type: application/vnd.oci.image.layer.v1.tar+gzip
|
|
Digest: sha256:330457f0054be4c07fbaeac90483ac4f534113ccd944fe16beb9e079a3ab3a36
|
|
Size: 60.05MB
|
|
Descriptor:
|
|
Media Type: application/vnd.oci.image.layer.v1.tar+gzip
|
|
Digest: sha256:771ee02be966963d69210ed8243baa8f322661858bae69d1c2d5d13fe4dc92ba
|
|
Size: 126B
|
|
Descriptor:
|
|
Media Type: application/vnd.oci.image.layer.v1.tar+gzip
|
|
Digest: sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1
|
|
Size: 32B
|
|
Annotations:
|
|
com.docker.official-images.bashbrew.arch: amd64
|
|
org.opencontainers.image.base.digest: sha256:6f9fd607b2e260d41c51207fb74010ba06931047760494f5f6d59854e7d55db4
|
|
org.opencontainers.image.base.name: buildpack-deps:trixie-scm
|
|
org.opencontainers.image.created: 2025-09-03T18:13:04Z
|
|
org.opencontainers.image.revision: be5c27da377afdeebf0c5747560bedd7f96ccb1c
|
|
org.opencontainers.image.source: https://github.com/docker-library/golang.git#be5c27da377afdeebf0c5747560bedd7f96ccb1c:1.25/trixie
|
|
org.opencontainers.image.url: https://hub.docker.com/_/golang
|
|
org.opencontainers.image.version: 1.25.1-trixie
|
|
`},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
var output bytes.Buffer
|
|
err := pprintManifest(&output, test.prefix, test.manifest)
|
|
require.NoErrorf(t, err, "pprint(%q, %#v)", test.prefix, test.manifest)
|
|
assert.Equal(t, test.expected, output.String())
|
|
})
|
|
}
|
|
}
|