Publish curated ITWorx_MCP_Hub source from 55011066f84f073ceabb35c99ddd5b65745e34f6

This commit is contained in:
Public Source Publisher committed 2026-10-14 07:15:12 +00:00
1 parent 020282a4b5
commit b35324a665
6 files changed
+653 -11

No files matched your search

+165
View File
@@ -0,0 +1,165 @@
{
"schema": "itworx.moby-v1-wire-golden/v1",
"goVersion": "go1.27.2",
"referenceMainSha256": "b700de1bf2e0d9414c388e9d4b61850e53dfe08a90497f592f45caff71eebbdd",
"sourceCommits": [
"89c5e8fd66634b6128fc4c0e6f1236e2540e46e0",
"5834c866f83c0bae2d0be28f38b92f32cd07ca38"
],
"cases": [
{
"dialect": "onbuild_null",
"config": {
"architecture": "amd64",
"os": "linux",
"created": "2026-10-14T00:01:02.123456789Z",
"container_config": {
"User": "constructor",
"Cmd": [
"µ<&>

"
],
"Labels": {
"z": "last",
"a": "first"
},
"StopTimeout": 0
},
"config": {
"User": "10001:10001",
"Cmd": [
"reference"
],
"Labels": {
"z": "µ<&>

",
"a": "first"
},
"StopTimeout": 0,
"Healthcheck": {
"Test": [
"CMD",
"true"
],
"Interval": 30000000000,
"Retries": 2
}
},
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
]
}
},
"blobs": [
{
"name": "blobs/sha256/e9e75851959057c9547c9692b88370291d478cbc2c4ef5752074971970f36b3c",
"payloadBase64": "eyJpZCI6IjRiZDY5YzRjYTE1OWQ4NzI0MGJmZTVlZTViM2ZmNjliZjUwZWY3YWJkMGQzZDc4NjA4MTgyNjJlZDU2Y2ViZGIiLCJjcmVhdGVkIjoiMTk3MC0wMS0wMVQwMDowMDowMFoiLCJjb250YWluZXJfY29uZmlnIjp7Ikhvc3RuYW1lIjoiIiwiRG9tYWlubmFtZSI6IiIsIlVzZXIiOiIiLCJBdHRhY2hTdGRpbiI6ZmFsc2UsIkF0dGFjaFN0ZG91dCI6ZmFsc2UsIkF0dGFjaFN0ZGVyciI6ZmFsc2UsIlR0eSI6ZmFsc2UsIk9wZW5TdGRpbiI6ZmFsc2UsIlN0ZGluT25jZSI6ZmFsc2UsIkVudiI6bnVsbCwiQ21kIjpudWxsLCJJbWFnZSI6IiIsIlZvbHVtZXMiOm51bGwsIldvcmtpbmdEaXIiOiIiLCJFbnRyeXBvaW50IjpudWxsLCJPbkJ1aWxkIjpudWxsLCJMYWJlbHMiOm51bGx9LCJvcyI6ImxpbnV4In0="
},
{
"name": "blobs/sha256/777fb43341dc2ede2a03d9c8e9d79f15d64a4be734f6f720e0a36653cf7d644b",
"payloadBase64": "eyJpZCI6ImRkZDkyMDY4YWNlMzA3NGQxY2NlYTZhNDE0ODAwMWUxYTIyYjY4M2FiZWM4NmViMTUyNjMwMzk5YThhZWY4YzkiLCJwYXJlbnQiOiI0YmQ2OWM0Y2ExNTlkODcyNDBiZmU1ZWU1YjNmZjY5YmY1MGVmN2FiZDBkM2Q3ODYwODE4MjYyZWQ1NmNlYmRiIiwiY3JlYXRlZCI6IjIwMjYtMTAtMTRUMDA6MDE6MDIuMTIzNDU2Nzg5WiIsImNvbnRhaW5lcl9jb25maWciOnsiSG9zdG5hbWUiOiIiLCJEb21haW5uYW1lIjoiIiwiVXNlciI6ImNvbnN0cnVjdG9yIiwiQXR0YWNoU3RkaW4iOmZhbHNlLCJBdHRhY2hTdGRvdXQiOmZhbHNlLCJBdHRhY2hTdGRlcnIiOmZhbHNlLCJUdHkiOmZhbHNlLCJPcGVuU3RkaW4iOmZhbHNlLCJTdGRpbk9uY2UiOmZhbHNlLCJFbnYiOm51bGwsIkNtZCI6WyLCtVx1MDAzY1x1MDAyNlx1MDAzZVx1MjAyOFx1MjAyOSJdLCJJbWFnZSI6IiIsIlZvbHVtZXMiOm51bGwsIldvcmtpbmdEaXIiOiIiLCJFbnRyeXBvaW50IjpudWxsLCJPbkJ1aWxkIjpudWxsLCJMYWJlbHMiOnsiYSI6ImZpcnN0IiwieiI6Imxhc3QifSwiU3RvcFRpbWVvdXQiOjB9LCJjb25maWciOnsiSG9zdG5hbWUiOiIiLCJEb21haW5uYW1lIjoiIiwiVXNlciI6IjEwMDAxOjEwMDAxIiwiQXR0YWNoU3RkaW4iOmZhbHNlLCJBdHRhY2hTdGRvdXQiOmZhbHNlLCJBdHRhY2hTdGRlcnIiOmZhbHNlLCJUdHkiOmZhbHNlLCJPcGVuU3RkaW4iOmZhbHNlLCJTdGRpbk9uY2UiOmZhbHNlLCJFbnYiOm51bGwsIkNtZCI6WyJyZWZlcmVuY2UiXSwiSGVhbHRoY2hlY2siOnsiVGVzdCI6WyJDTUQiLCJ0cnVlIl0sIkludGVydmFsIjozMDAwMDAwMDAwMCwiUmV0cmllcyI6Mn0sIkltYWdlIjoiIiwiVm9sdW1lcyI6bnVsbCwiV29ya2luZ0RpciI6IiIsIkVudHJ5cG9pbnQiOm51bGwsIk9uQnVpbGQiOm51bGwsIkxhYmVscyI6eyJhIjoiZmlyc3QiLCJ6IjoiwrVcdTAwM2NcdTAwMjZcdTAwM2VcdTIwMjhcdTIwMjkifSwiU3RvcFRpbWVvdXQiOjB9LCJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsIm9zIjoibGludXgifQ=="
}
]
},
{
"dialect": "onbuild_omitempty",
"config": {
"architecture": "amd64",
"os": "linux",
"created": "2026-10-14T00:01:02.123456789Z",
"container_config": {
"User": "constructor",
"Cmd": [
"µ<&>

"
],
"Labels": {
"z": "last",
"a": "first"
},
"StopTimeout": 0
},
"config": {
"User": "10001:10001",
"Cmd": [
"reference"
],
"Labels": {
"z": "µ<&>

",
"a": "first"
},
"StopTimeout": 0,
"Healthcheck": {
"Test": [
"CMD",
"true"
],
"Interval": 30000000000,
"Retries": 2
}
},
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
]
}
},
"blobs": [
{
"name": "blobs/sha256/cab874d9c7a96fe390da5177f6b0beaa00ba368a72269d8e566b0274b9ea16c6",
"payloadBase64": "eyJpZCI6IjdhOWExYmE1MTFjYTkwYWZkODUxM2YyNzVhMGJkNWRjODNjZWY2NDJjMjkzYjUyZTYxYTkzNTBjZThiMzk0OTMiLCJjcmVhdGVkIjoiMTk3MC0wMS0wMVQwMDowMDowMFoiLCJjb250YWluZXJfY29uZmlnIjp7Ikhvc3RuYW1lIjoiIiwiRG9tYWlubmFtZSI6IiIsIlVzZXIiOiIiLCJBdHRhY2hTdGRpbiI6ZmFsc2UsIkF0dGFjaFN0ZG91dCI6ZmFsc2UsIkF0dGFjaFN0ZGVyciI6ZmFsc2UsIlR0eSI6ZmFsc2UsIk9wZW5TdGRpbiI6ZmFsc2UsIlN0ZGluT25jZSI6ZmFsc2UsIkVudiI6bnVsbCwiQ21kIjpudWxsLCJJbWFnZSI6IiIsIlZvbHVtZXMiOm51bGwsIldvcmtpbmdEaXIiOiIiLCJFbnRyeXBvaW50IjpudWxsLCJMYWJlbHMiOm51bGx9LCJvcyI6ImxpbnV4In0="
},
{
"name": "blobs/sha256/f51881ca0e4436a2ca17e4d94fdc08590cabe3aa345c5ae290f899869ab824ba",
"payloadBase64": "eyJpZCI6IjNiMWFhZTBhMjU4NmU0MWI1YzdiN2UzODdhYTQxNjYxOWU5YjBlYWRmMTU4Nzg1OTE3ZDJlMGMxY2ZlNzVmYzciLCJwYXJlbnQiOiI3YTlhMWJhNTExY2E5MGFmZDg1MTNmMjc1YTBiZDVkYzgzY2VmNjQyYzI5M2I1MmU2MWE5MzUwY2U4YjM5NDkzIiwiY3JlYXRlZCI6IjIwMjYtMTAtMTRUMDA6MDE6MDIuMTIzNDU2Nzg5WiIsImNvbnRhaW5lcl9jb25maWciOnsiSG9zdG5hbWUiOiIiLCJEb21haW5uYW1lIjoiIiwiVXNlciI6ImNvbnN0cnVjdG9yIiwiQXR0YWNoU3RkaW4iOmZhbHNlLCJBdHRhY2hTdGRvdXQiOmZhbHNlLCJBdHRhY2hTdGRlcnIiOmZhbHNlLCJUdHkiOmZhbHNlLCJPcGVuU3RkaW4iOmZhbHNlLCJTdGRpbk9uY2UiOmZhbHNlLCJFbnYiOm51bGwsIkNtZCI6WyLCtVx1MDAzY1x1MDAyNlx1MDAzZVx1MjAyOFx1MjAyOSJdLCJJbWFnZSI6IiIsIlZvbHVtZXMiOm51bGwsIldvcmtpbmdEaXIiOiIiLCJFbnRyeXBvaW50IjpudWxsLCJMYWJlbHMiOnsiYSI6ImZpcnN0IiwieiI6Imxhc3QifSwiU3RvcFRpbWVvdXQiOjB9LCJjb25maWciOnsiSG9zdG5hbWUiOiIiLCJEb21haW5uYW1lIjoiIiwiVXNlciI6IjEwMDAxOjEwMDAxIiwiQXR0YWNoU3RkaW4iOmZhbHNlLCJBdHRhY2hTdGRvdXQiOmZhbHNlLCJBdHRhY2hTdGRlcnIiOmZhbHNlLCJUdHkiOmZhbHNlLCJPcGVuU3RkaW4iOmZhbHNlLCJTdGRpbk9uY2UiOmZhbHNlLCJFbnYiOm51bGwsIkNtZCI6WyJyZWZlcmVuY2UiXSwiSGVhbHRoY2hlY2siOnsiVGVzdCI6WyJDTUQiLCJ0cnVlIl0sIkludGVydmFsIjozMDAwMDAwMDAwMCwiUmV0cmllcyI6Mn0sIkltYWdlIjoiIiwiVm9sdW1lcyI6bnVsbCwiV29ya2luZ0RpciI6IiIsIkVudHJ5cG9pbnQiOm51bGwsIkxhYmVscyI6eyJhIjoiZmlyc3QiLCJ6IjoiwrVcdTAwM2NcdTAwMjZcdTAwM2VcdTIwMjhcdTIwMjkifSwiU3RvcFRpbWVvdXQiOjB9LCJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsIm9zIjoibGludXgifQ=="
}
]
},
{
"dialect": "onbuild_null",
"config": {
"architecture": "amd64",
"os": "linux",
"config": {
"User": "10001:10001"
},
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
]
}
},
"blobs": [
{
"name": "blobs/sha256/8d095090ae9bff4df0f104355b1d57c36f538a9ab088bd9962ecc042c84fbd2c",
"payloadBase64": "eyJpZCI6IjUzODdhM2U3NzM3YWRkMGFlOTRhZDZiZGM2ZmYwYzc5MjY5OTEyNDg1NGE3Y2MxNDgxM2FmNDViNzE5Yjk2OTAiLCJjcmVhdGVkIjpudWxsLCJjb250YWluZXJfY29uZmlnIjp7Ikhvc3RuYW1lIjoiIiwiRG9tYWlubmFtZSI6IiIsIlVzZXIiOiIiLCJBdHRhY2hTdGRpbiI6ZmFsc2UsIkF0dGFjaFN0ZG91dCI6ZmFsc2UsIkF0dGFjaFN0ZGVyciI6ZmFsc2UsIlR0eSI6ZmFsc2UsIk9wZW5TdGRpbiI6ZmFsc2UsIlN0ZGluT25jZSI6ZmFsc2UsIkVudiI6bnVsbCwiQ21kIjpudWxsLCJJbWFnZSI6IiIsIlZvbHVtZXMiOm51bGwsIldvcmtpbmdEaXIiOiIiLCJFbnRyeXBvaW50IjpudWxsLCJPbkJ1aWxkIjpudWxsLCJMYWJlbHMiOm51bGx9LCJjb25maWciOnsiSG9zdG5hbWUiOiIiLCJEb21haW5uYW1lIjoiIiwiVXNlciI6IjEwMDAxOjEwMDAxIiwiQXR0YWNoU3RkaW4iOmZhbHNlLCJBdHRhY2hTdGRvdXQiOmZhbHNlLCJBdHRhY2hTdGRlcnIiOmZhbHNlLCJUdHkiOmZhbHNlLCJPcGVuU3RkaW4iOmZhbHNlLCJTdGRpbk9uY2UiOmZhbHNlLCJFbnYiOm51bGwsIkNtZCI6bnVsbCwiSW1hZ2UiOiIiLCJWb2x1bWVzIjpudWxsLCJXb3JraW5nRGlyIjoiIiwiRW50cnlwb2ludCI6bnVsbCwiT25CdWlsZCI6bnVsbCwiTGFiZWxzIjpudWxsfSwiYXJjaGl0ZWN0dXJlIjoiYW1kNjQiLCJvcyI6ImxpbnV4In0="
}
]
},
{
"dialect": "onbuild_omitempty",
"config": {
"architecture": "amd64",
"os": "linux",
"config": {
"User": "10001:10001"
},
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
]
}
},
"blobs": [
{
"name": "blobs/sha256/466fa2f08fccd0fcd2e8debbdd88466c6a4e3e1d5d53df5f02abe0c96afe8cfb",
"payloadBase64": "eyJpZCI6ImIyYmMzM2I4MjJlYTYyYjI2ZGZlZjViZDUxYWU2Yjc0NDI5Y2JjYjY3OWEwMmE3ZDUwNzEzZDdhZTlmOTQxNTciLCJjcmVhdGVkIjpudWxsLCJjb250YWluZXJfY29uZmlnIjp7Ikhvc3RuYW1lIjoiIiwiRG9tYWlubmFtZSI6IiIsIlVzZXIiOiIiLCJBdHRhY2hTdGRpbiI6ZmFsc2UsIkF0dGFjaFN0ZG91dCI6ZmFsc2UsIkF0dGFjaFN0ZGVyciI6ZmFsc2UsIlR0eSI6ZmFsc2UsIk9wZW5TdGRpbiI6ZmFsc2UsIlN0ZGluT25jZSI6ZmFsc2UsIkVudiI6bnVsbCwiQ21kIjpudWxsLCJJbWFnZSI6IiIsIlZvbHVtZXMiOm51bGwsIldvcmtpbmdEaXIiOiIiLCJFbnRyeXBvaW50IjpudWxsLCJMYWJlbHMiOm51bGx9LCJjb25maWciOnsiSG9zdG5hbWUiOiIiLCJEb21haW5uYW1lIjoiIiwiVXNlciI6IjEwMDAxOjEwMDAxIiwiQXR0YWNoU3RkaW4iOmZhbHNlLCJBdHRhY2hTdGRvdXQiOmZhbHNlLCJBdHRhY2hTdGRlcnIiOmZhbHNlLCJUdHkiOmZhbHNlLCJPcGVuU3RkaW4iOmZhbHNlLCJTdGRpbk9uY2UiOmZhbHNlLCJFbnYiOm51bGwsIkNtZCI6bnVsbCwiSW1hZ2UiOiIiLCJWb2x1bWVzIjpudWxsLCJXb3JraW5nRGlyIjoiIiwiRW50cnlwb2ludCI6bnVsbCwiTGFiZWxzIjpudWxsfSwiYXJjaGl0ZWN0dXJlIjoiYW1kNjQiLCJvcyI6ImxpbnV4In0="
}
]
}
]
}
+102
View File
@@ -0,0 +1,102 @@
// Reference-only Moby saveConfig/CreateID oracle. The generator supplies the
// source-pinned Config/V1Image declarations; this never imports Hub code.
package main
import (
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"reflect"
)
type blob struct {
Name string `json:"name"`
Payload string `json:"payloadBase64"`
}
type result struct {
Dialect string `json:"dialect"`
Config json.RawMessage `json:"config"`
Blobs []blob `json:"blobs"`
}
func sum(data []byte) string { return fmt.Sprintf("%x", sha256.Sum256(data)) }
func encoded(value any) []byte {
data, err := json.Marshal(value)
if err != nil {
panic(err)
}
return data
}
func derive[T any](dialect string, raw json.RawMessage) result {
var image struct {
RootFS struct {
DiffIDs []string `json:"diff_ids"`
} `json:"rootfs"`
}
if err := json.Unmarshal(raw, &image); err != nil {
panic(err)
}
// Image.Parent shadows V1Image.Parent in Moby; these golden inputs do not
// depend on an old builder's parent field.
var original T
if err := json.Unmarshal(raw, &original); err != nil {
panic(err)
}
chain, parent := "", ""
output := result{Dialect: dialect, Config: raw, Blobs: []blob{}}
for index, diff := range image.RootFS.DiffIDs {
if chain == "" {
chain = diff
} else {
chain = "sha256:" + sum([]byte(chain+" "+diff))
}
var legacy T
if index == len(image.RootFS.DiffIDs)-1 {
legacy = original
} else {
if err := json.Unmarshal([]byte(`{"created":"1970-01-01T00:00:00Z"}`), &legacy); err != nil {
panic(err)
}
}
reflected := reflect.ValueOf(&legacy).Elem()
reflected.FieldByName("ID").SetString("")
var preimage map[string]json.RawMessage
if err := json.Unmarshal(encoded(legacy), &preimage); err != nil {
panic(err)
}
preimage["layer_id"] = encoded(chain)
if parent != "" {
preimage["parent"] = encoded("sha256:" + parent)
}
identity := sum(encoded(preimage))
reflected.FieldByName("ID").SetString(identity)
if parent != "" {
reflected.FieldByName("Parent").SetString(parent)
}
reflected.FieldByName("OS").SetString("linux")
payload := encoded(legacy)
output.Blobs = append(output.Blobs, blob{
Name: "blobs/sha256/" + sum(payload), Payload: base64.StdEncoding.EncodeToString(payload),
})
parent = identity
}
return output
}
func main() {
data, err := os.ReadFile(os.Args[1])
if err != nil {
panic(err)
}
var inputs []json.RawMessage
if err := json.Unmarshal(data, &inputs); err != nil {
panic(err)
}
outputs := []result{}
for _, input := range inputs {
outputs = append(outputs, derive[OldV1]("onbuild_null", input))
outputs = append(outputs, derive[NewV1]("onbuild_omitempty", input))
}
fmt.Println(string(encoded(outputs)))
}
+193 -7
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
import base64
import hashlib
import io
import json
@@ -123,6 +124,8 @@ def _write_archive_fixture(path: Path, members: dict[str, bytes | None]) -> None
def _docker29_oci_fixture(
tmp_path: Path,
*,
layer_count: int = 1,
) -> tuple[Path, dict[str, bytes | None], dict[str, dict[str, str]], dict[str, str]]:
tmp_path.mkdir(parents=True, exist_ok=True)
revision = "a" * 40
@@ -143,6 +146,19 @@ def _docker29_oci_fixture(
layer_digest = "sha256:" + hashlib.sha256(layer).hexdigest()
layer_name = "blobs/sha256/" + layer_digest.split(":", 1)[1]
members: dict[str, bytes | None] = {"blobs": None, "blobs/sha256": None, layer_name: layer}
layer_rows = [(layer_digest, layer_name, layer)]
if layer_count == 2:
stream = io.BytesIO()
with tarfile.open(fileobj=stream, mode="w:", format=tarfile.USTAR_FORMAT) as archive:
content = b"second layer verifies prefix chain and legacy parent binding"
info = tarfile.TarInfo("second.txt")
info.size = len(content)
archive.addfile(info, io.BytesIO(content))
payload = stream.getvalue()
digest = "sha256:" + hashlib.sha256(payload).hexdigest()
name = "blobs/sha256/" + digest.split(":", 1)[1]
members[name] = payload
layer_rows.append((digest, name, payload))
images: dict[str, dict[str, str]] = {}
docker_manifest: list[dict[str, object]] = []
index_descriptors: list[dict[str, object]] = []
@@ -169,7 +185,7 @@ def _docker29_oci_fixture(
"architecture": "amd64",
"os": "linux",
"config": {"User": "10001:10001", "Labels": labels, "Cmd": [service]},
"rootfs": {"type": "layers", "diff_ids": [layer_digest]},
"rootfs": {"type": "layers", "diff_ids": [row[0] for row in layer_rows]},
}
config_bytes = json.dumps(config, sort_keys=True, separators=(",", ":")).encode()
image_id = "sha256:" + hashlib.sha256(config_bytes).hexdigest()
@@ -180,13 +196,14 @@ def _docker29_oci_fixture(
{
"Config": config_name,
"RepoTags": [reference],
"Layers": [layer_name],
"Layers": [row[1] for row in layer_rows],
"LayerSources": {
layer_digest: {
digest: {
"mediaType": "application/vnd.oci.image.layer.v1.tar",
"size": len(layer),
"digest": layer_digest,
"size": len(data),
"digest": digest,
}
for digest, _name, data in layer_rows
},
}
)
@@ -201,9 +218,10 @@ def _docker29_oci_fixture(
"layers": [
{
"mediaType": "application/vnd.oci.image.layer.v1.tar",
"digest": layer_digest,
"size": len(layer),
"digest": digest,
"size": len(data),
}
for digest, _name, data in layer_rows
],
}
image_manifest_bytes = json.dumps(
@@ -466,6 +484,174 @@ def test_docker_oci_index_can_canonicalize_external_reference_aliases(tmp_path:
assert normalized.is_file()
@pytest.mark.parametrize(
"case",
json.loads((ROOT / "tests/fixtures/moby_v1_golden.json").read_text(encoding="utf-8"))["cases"],
)
def test_independent_go_reference_matches_exact_legacy_wire_bytes(case):
expected = {
row["name"]: base64.b64decode(row["payloadBase64"], validate=True) for row in case["blobs"]
}
actual = producer._moby_v1_metadata(
case["config"], legacy_onbuild=case["dialect"] == "onbuild_null"
)
assert actual == expected
def test_go_reference_fixture_is_bound_to_its_reproducible_generator():
fixture = json.loads((ROOT / "tests/fixtures/moby_v1_golden.json").read_text(encoding="utf-8"))
assert fixture["schema"] == "itworx.moby-v1-wire-golden/v1"
assert fixture["goVersion"] == "go1.27.2"
assert (
fixture["referenceMainSha256"]
== hashlib.sha256(
(ROOT / "tests/fixtures/moby_v1_reference/main.go").read_bytes()
).hexdigest()
)
assert fixture["sourceCommits"] == [
"89c5e8fd66634b6128fc4c0e6f1236e2540e46e0",
"5834c866f83c0bae2d0be28f38b92f32cd07ca38",
]
def _moby_v1_fixture(members, *, legacy_onbuild=True):
# Faithful classic Moby saveConfig/CreateID serialization for the fixed
# single-layer images above, independent of the production normalizer.
zero = {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": False,
"AttachStdout": False,
"AttachStderr": False,
"Tty": False,
"OpenStdin": False,
"StdinOnce": False,
"Env": None,
"Cmd": None,
"Image": "",
"Volumes": None,
"WorkingDir": "",
"Entrypoint": None,
"OnBuild": None,
"Labels": None,
}
if not legacy_onbuild:
del zero["OnBuild"]
result = dict(members)
added = []
for row in json.loads(members["manifest.json"]):
config = json.loads(members[row["Config"]])
runtime = {**zero, **config["config"]}
parent = ""
chain = ""
diffs = config["rootfs"]["diff_ids"]
for index, diff in enumerate(diffs):
chain = (
diff
if not chain
else "sha256:" + hashlib.sha256((chain + " " + diff).encode()).hexdigest()
)
if index == len(diffs) - 1:
body = {
"created": None,
"container_config": zero,
"config": runtime,
"architecture": "amd64",
"os": "linux",
}
else:
body = {"created": "1970-01-01T00:00:00Z", "container_config": zero}
preimage = {**body, "layer_id": chain}
if parent:
preimage["parent"] = "sha256:" + parent
preimage = dict(sorted(preimage.items()))
legacy_id = hashlib.sha256(
json.dumps(preimage, separators=(",", ":")).encode()
).hexdigest()
saved = {"id": legacy_id}
if parent:
saved["parent"] = parent
saved.update(body)
saved["os"] = "linux"
payload = json.dumps(saved, separators=(",", ":")).encode()
name = "blobs/sha256/" + hashlib.sha256(payload).hexdigest()
result[name] = payload
added.append(name)
parent = legacy_id
return result, added
@pytest.mark.parametrize("legacy_onbuild", [True, False])
@pytest.mark.parametrize("layer_count", [1, 2])
def test_moby_v1_compatibility_metadata_preserves_exact_importer_bytes(
tmp_path, legacy_onbuild, layer_count
):
raw, members, images, metadata = _docker29_oci_fixture(tmp_path, layer_count=layer_count)
expected = tmp_path / "expected.tar"
producer._normalize_docker_archive(raw, expected, images, metadata=metadata)
extended, _added = _moby_v1_fixture(members, legacy_onbuild=legacy_onbuild)
raw_compat = tmp_path / "with-compat.tar"
_write_archive_fixture(raw_compat, extended)
actual = tmp_path / "actual.tar"
producer._normalize_docker_archive(raw_compat, actual, images, metadata=metadata)
assert actual.read_bytes() == expected.read_bytes()
def test_content_addressed_foreign_json_is_still_rejected(tmp_path):
_raw, members, images, metadata = _docker29_oci_fixture(tmp_path)
extended, _added = _moby_v1_fixture(members)
payload = b'{"id":"foreign","created":null,"os":"linux"}'
extended["blobs/sha256/" + hashlib.sha256(payload).hexdigest()] = payload
raw = tmp_path / "foreign.tar"
_write_archive_fixture(raw, extended)
output = tmp_path / "foreign-output.tar"
with pytest.raises(producer.BuildRejected):
producer._normalize_docker_archive(raw, output, images, metadata=metadata)
assert not output.exists()
@pytest.mark.parametrize(
"change", ["default", "unknown", "parent", "chain", "config", "order", "id"]
)
def test_self_consistent_foreign_v1_metadata_never_extends_archive_inventory(tmp_path, change):
_raw, members, images, metadata = _docker29_oci_fixture(tmp_path, layer_count=2)
extended, added = _moby_v1_fixture(members)
original_name = added[-1]
body = json.loads(extended.pop(original_name))
first = json.loads(members["manifest.json"])[0]
diffs = json.loads(members[first["Config"]])["rootfs"]["diff_ids"]
chain = "sha256:" + hashlib.sha256((diffs[0] + " " + diffs[1]).encode()).hexdigest()
if change == "default":
body["container_config"]["Tty"] = True
elif change == "unknown":
body["unexpected"] = "fixture-sensitive-foreign-field"
elif change == "parent":
body["parent"] = "e" * 64
elif change == "chain":
chain = "sha256:" + "e" * 64
elif change == "config":
body["config"]["User"] = "root"
elif change == "order":
body["container_config"] = dict(sorted(body["container_config"].items()))
preimage = {key: value for key, value in body.items() if key not in {"id", "parent"}}
preimage.update(layer_id=chain, parent="sha256:" + body["parent"])
body["id"] = hashlib.sha256(
json.dumps(dict(sorted(preimage.items())), separators=(",", ":")).encode()
).hexdigest()
if change == "id":
body["id"] = "e" * 64
payload = json.dumps(body, separators=(",", ":")).encode()
extended["blobs/sha256/" + hashlib.sha256(payload).hexdigest()] = payload
raw = tmp_path / "spoof.tar"
_write_archive_fixture(raw, extended)
output = tmp_path / "spoof-output.tar"
with pytest.raises(producer.BuildRejected) as error:
producer._normalize_docker_archive(raw, output, images, metadata=metadata)
assert "fixture-sensitive" not in str(error.value)
assert not output.exists()
def test_docker29_oci_normalizer_rejects_unreferenced_blobs_and_config_mismatch(
tmp_path: Path,
) -> None: