diff --git a/.test/deploy-dry-run-test.json b/.test/deploy-dry-run-test.json new file mode 100644 index 0000000..640121a --- /dev/null +++ b/.test/deploy-dry-run-test.json @@ -0,0 +1,385 @@ +{ + "type": "blob", + "refs": [ + "localhost:3000/test@sha256:1a51828d59323e0e02522c45652b6a7a44a032b464b06d574f067d2358b0e9f1" + ], + "data": "YnVmZnkgdGhlIHZhbXBpcmUgc2xheWVyCg==" +} +{ + "type": "blob", + "refs": [ + "localhost:3000/test@sha256:bdc1ce731138e680ada95089dded3015b8e1570d9a70216867a2a29801a747b3" + ], + "data": "ewogICJmb28iOiAiYmFyIiwKICAiYmF6IjogWwogICAgImJ1enoiLAogICAgImJ1enoiLAogICAgImJ1enoiCiAgXQp9Cg==" +} +{ + "type": "blob", + "refs": [ + "localhost:3000/test@sha256:680c1729a6d4a34f69123f5936cfd4f2cb82a008951241cfc499f9e52996b380" + ], + "data": "Impzb24gc3RyaW5nIgo=" +} +{ + "type": "blob", + "refs": [ + "localhost:3000/true@sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e" + ], + "data": "ewoJImFyY2hpdGVjdHVyZSI6ICJhbWQ2NCIsCgkiY29uZmlnIjogewoJCSJDbWQiOiBbCgkJCSIvdHJ1ZSIKCQldCgl9LAoJImNyZWF0ZWQiOiAiMjAyMy0wMi0wMVQwNjo1MToxMVoiLAoJImhpc3RvcnkiOiBbCgkJewoJCQkiY3JlYXRlZCI6ICIyMDIzLTAyLTAxVDA2OjUxOjExWiIsCgkJCSJjcmVhdGVkX2J5IjogImh0dHBzOi8vZ2l0aHViLmNvbS90aWFub24vZG9ja2VyZmlsZXMvdHJlZS9tYXN0ZXIvdHJ1ZSIKCQl9CgldLAoJIm9zIjogImxpbnV4IiwKCSJyb290ZnMiOiB7CgkJImRpZmZfaWRzIjogWwoJCQkic2hhMjU2OjY1YjVhNDU5M2NjNjFkM2VhNmQzNTVmYjk3YzA0MzBkODIwZWUyMWFhODUzNWY1ZGU0NWU3NWMzMTk1NGI3NDMiCgkJXSwKCQkidHlwZSI6ICJsYXllcnMiCgl9Cn0K" +} +{ + "type": "blob", + "refs": [ + "localhost:3000/true@sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6" + ], + "data": "H4sIAAAAAAACAyspKk1loDEwAAJTU1MwDQTotIGhuQmcDRE3MzM0YlAwYKADKC0uSSxSUGAYoaDe1ceNiZERzmdisGMA8SoYHMB8Byx6HBgsGGA6QDQrmiwyXQPl1cDlIUG9wYaflWEUDDgAAIAGdJIABAAA" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/true:oci@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/foo/true:oci@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/true:latest@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/foo/true:latest@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/true:0@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/foo/true:0@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/true:1@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/foo/true:1@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/true:2@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/foo/true:2@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/true:3@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/foo/true:3@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/true:4@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/foo/true:4@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/true:5@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/foo/true:5@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/true:6@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/foo/true:6@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/true:7@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/foo/true:7@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/true:8@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/foo/true:8@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/true:9@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/foo/true:9@sha256:4a4149cb9b710b845e9e6b57d66408ca84472937a8aa431b4dddd1e6b122e9f2" + ], + "lookup": { + "": "localhost:3000/true", + "sha256:1c51fc286aa95d9413226599576bafa38490b1e292375c90de095855b64caea6": "localhost:3000/true" + }, + "data": "ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1NjoyNWJlODIyNTMzMzZmMGI4YzQzNDdiYzRlY2JiY2RjODVkMGUwZjExOGNjZjhkYzJlMTE5YzBhNDdhMGE0ODZlIiwKICAgICJzaXplIjogMzk2CiAgfSwKICAibGF5ZXJzIjogWwogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxYzUxZmMyODZhYTk1ZDk0MTMyMjY1OTk1NzZiYWZhMzg0OTBiMWUyOTIzNzVjOTBkZTA5NTg1NWI2NGNhZWE2IiwKICAgICAgInNpemUiOiAxMTcKICAgIH0KICBdCn0K", + "mediaType": "application/vnd.oci.image.manifest.v1+json" +} +{ + "type": "blob", + "refs": [ + "localhost:3000/test-mount@sha256:1a51828d59323e0e02522c45652b6a7a44a032b464b06d574f067d2358b0e9f1" + ], + "lookup": { + "": "localhost:3000/test" + }, + "copyFrom": "localhost:3000/test@sha256:1a51828d59323e0e02522c45652b6a7a44a032b464b06d574f067d2358b0e9f1" +} +{ + "type": "blob", + "refs": [ + "localhost:3000/cirros@sha256:6cef03f2716ee8ba76999750aee1a742888ccd0db923be33ff6a410d87f4277d" + ], + "lookup": { + "": "oisupport/staging-amd64" + }, + "copyFrom": "oisupport/staging-amd64@sha256:6cef03f2716ee8ba76999750aee1a742888ccd0db923be33ff6a410d87f4277d" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/cirros" + ], + "lookup": { + "": "oisupport/staging-amd64:34bb44c7d8b6fb7a337fcee0afa7c3a84148e35db6ab83041714c3e6d4c6238b" + }, + "copyFrom": "oisupport/staging-amd64:34bb44c7d8b6fb7a337fcee0afa7c3a84148e35db6ab83041714c3e6d4c6238b" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/test@sha256:4077658bc7e39f02f81d1682fe49f66b3db2c420813e43f5db0c53046167c12f" + ], + "lookup": { + "": "tianon/test", + "sha256:4077658bc7e39f02f81d1682fe49f66b3db2c420813e43f5db0c53046167c12f": "tianon/test" + }, + "copyFrom": "tianon/test@sha256:4077658bc7e39f02f81d1682fe49f66b3db2c420813e43f5db0c53046167c12f" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/test@sha256:96a7a809d1b336011450164564154a5e1c257dc7eb9081e28638537c472ccb90" + ], + "lookup": { + "": "tianon/test", + "sha256:96a7a809d1b336011450164564154a5e1c257dc7eb9081e28638537c472ccb90": "tianon/test" + }, + "copyFrom": "tianon/test@sha256:96a7a809d1b336011450164564154a5e1c257dc7eb9081e28638537c472ccb90" +} +{ + "type": "manifest", + "refs": [ + "localhost:3000/test:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee@sha256:73614cc99c500aa4fa061368ed349df24a81844e3c2e6d0c31f290a7c8d73c22" + ], + "lookup": { + "": "tianon/test" + }, + "copyFrom": "tianon/test@sha256:73614cc99c500aa4fa061368ed349df24a81844e3c2e6d0c31f290a7c8d73c22" +} diff --git a/.test/test.sh b/.test/test.sh index 2da0a27..4d5f884 100755 --- a/.test/test.sh +++ b/.test/test.sh @@ -250,7 +250,15 @@ if [ -n "$doDeploy" ]; then empty ')" # stored in a variable for easier debugging ("bash -x") - time "$coverage/bin/deploy" <<<"$json" + time "$coverage/bin/deploy" --dry-run --parallel <<<"$json" > "$dir/deploy-dry-run-test.json" + # port is random, so let's de-randomize it: + sed -i -e "s/localhost:$registryPort/localhost:3000/g" "$dir/deploy-dry-run-test.json" + + time "$coverage/bin/deploy" --parallel <<<"$json" + + # now that we're done with deploying, a second dry-run should come back empty (this time without parallel to test other codepaths) + time empty="$("$coverage/bin/deploy" --dry-run <<<"$json")" + ( set -x; test -z "$empty" ) docker rm -vf meta-scripts-test-registry trap - EXIT diff --git a/Jenkinsfile.deploy b/Jenkinsfile.deploy index 2e8297e..fce3956 100644 --- a/Jenkinsfile.deploy +++ b/Jenkinsfile.deploy @@ -106,7 +106,7 @@ node('put-shared') { ansiColor('xterm') { ./.go-env.sh go build -trimpath -o bin/deploy ./cmd/deploy fi ) - .scripts/bin/deploy < filtered-deploy.json + .scripts/bin/deploy --parallel < filtered-deploy.json ''' } } diff --git a/cmd/deploy/input.go b/cmd/deploy/input.go index 8e8e80c..a3191f9 100644 --- a/cmd/deploy/input.go +++ b/cmd/deploy/input.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + "maps" "github.com/docker-library/meta-scripts/registry" @@ -43,10 +44,17 @@ type inputNormalized struct { Lookup map[ociregistry.Digest]registry.Reference `json:"lookup,omitempty"` // Data and CopyFrom are mutually exclusive - Data []byte `json:"data"` - CopyFrom *registry.Reference `json:"copyFrom"` + Data []byte `json:"data,omitempty"` + CopyFrom *registry.Reference `json:"copyFrom,omitempty"` - Do func(ctx context.Context, dstRef registry.Reference) (ociregistry.Descriptor, error) `json:"-"` + // if CopyFrom is nil and Type is manifest, this will be set (used by "do") + MediaType string `json:"mediaType,omitempty"` +} + +func (normal inputNormalized) clone() inputNormalized { + // normal.Lookup is the only thing we have concurrency issues with, so it's the only thing we'll explicitly clone 😇 + normal.Lookup = maps.Clone(normal.Lookup) + return normal } func normalizeInputRefs(deployType deployType, rawRefs []string) ([]registry.Reference, ociregistry.Digest, error) { @@ -215,13 +223,23 @@ func NormalizeInput(raw inputRaw) (inputNormalized, error) { normal.Refs[i].Digest = refsDigest } + // if we have a digest and we're performing a copy, the tag we're copying *from* is no longer relevant information + if refsDigest != "" && normal.CopyFrom != nil { + normal.CopyFrom.Tag = "" + } + // explicitly clear tag and digest from lookup entries (now that we've inferred any "CopyFrom" out of them, they no longer have any meaning) for d, ref := range normal.Lookup { + if d == "" && refsDigest == "" && ref.Tag != "" && normal.CopyFrom != nil && ref.Tag == normal.CopyFrom.Tag { + // let the "fallback" ref keep a tag when it's the tag we're copying and there's no known digest (this allows our normalized objects to still be completely valid "raw" inputs) + continue + } ref.Tag = "" ref.Digest = "" normal.Lookup[d] = ref } + // front-load some validation / data extraction for "normal.do" to work switch normal.Type { case typeManifest: if normal.CopyFrom == nil { @@ -240,33 +258,98 @@ func NormalizeInput(raw inputRaw) (inputNormalized, error) { // and our logic for pushing children needs to know the mediaType (see the GHSAs referenced above) return normal, fmt.Errorf("%s: pushing manifest but missing 'mediaType'", debugId) } - normal.Do = func(ctx context.Context, dstRef registry.Reference) (ociregistry.Descriptor, error) { - return registry.EnsureManifest(ctx, dstRef, normal.Data, mediaTypeHaver.MediaType, normal.Lookup) - } + normal.MediaType = mediaTypeHaver.MediaType + } + + case typeBlob: + if normal.CopyFrom != nil && normal.CopyFrom.Digest == "" { + return normal, fmt.Errorf("%s: blobs are always by-digest, and thus need a digest: %s", debugId, normal.CopyFrom) + } + + default: + panic("unknown type: " + string(normal.Type)) + // panic instead of error because this should've already been handled/normalized above (so this is a coding error, not a runtime error) + } + + return normal, nil +} + +// WARNING: many of these codepaths will end up writing to "normal.Lookup", which because it's a map is passed by reference, so this method is *not* safe for concurrent invocation on a single "normal" object! see "normal.clone" (above) +func (normal inputNormalized) do(ctx context.Context, dstRef registry.Reference) (ociregistry.Descriptor, error) { + switch normal.Type { + case typeManifest: + if normal.CopyFrom == nil { + // TODO panic on bad data, like MediaType being empty? + return registry.EnsureManifest(ctx, dstRef, normal.Data, normal.MediaType, normal.Lookup) } else { - normal.Do = func(ctx context.Context, dstRef registry.Reference) (ociregistry.Descriptor, error) { - return registry.CopyManifest(ctx, *normal.CopyFrom, dstRef, normal.Lookup) - } + return registry.CopyManifest(ctx, *normal.CopyFrom, dstRef, normal.Lookup) } case typeBlob: if normal.CopyFrom == nil { - normal.Do = func(ctx context.Context, dstRef registry.Reference) (ociregistry.Descriptor, error) { - return registry.EnsureBlob(ctx, dstRef, int64(len(normal.Data)), bytes.NewReader(normal.Data)) - } + return registry.EnsureBlob(ctx, dstRef, int64(len(normal.Data)), bytes.NewReader(normal.Data)) } else { - if normal.CopyFrom.Digest == "" { - return normal, fmt.Errorf("%s: blobs are always by-digest, and thus need a digest: %s", debugId, normal.CopyFrom) + return registry.CopyBlob(ctx, *normal.CopyFrom, dstRef) + } + + default: + panic("unknown type: " + string(normal.Type)) + // panic instead of error because this should've already been handled/normalized above (so this is a coding error, not a runtime error) + } +} + +// "do", but doesn't mutate state at all (just tells us whether "do" would've done anything) +func (normal inputNormalized) dryRun(ctx context.Context, dstRef registry.Reference) (bool, error) { + targetDigest := dstRef.Digest + var lookupType registry.LookupType + switch normal.Type { + case typeManifest: + lookupType = registry.LookupTypeManifest + if targetDigest == "" { + // if we don't have a digest here, it must be because we're copying from tag to tag, so we'll just assume normal.CopyFrom is non-nil and let the runtime panic for us if the normalization above doesn't have our back + r, err := registry.Lookup(ctx, *normal.CopyFrom, ®istry.LookupOptions{ + Type: lookupType, + Head: true, + }) + if err != nil { + return true, err + } + if r == nil { + return true, fmt.Errorf("%s: manifest-to-copy (%s) is 404", dstRef.String(), normal.CopyFrom.String()) + } + targetDigest = r.Descriptor().Digest + r.Close() + if targetDigest == "" { + return true, fmt.Errorf("%s: manifest-to-copy (%s) is missing digest!", dstRef.String(), normal.CopyFrom.String()) } - normal.Do = func(ctx context.Context, dstRef registry.Reference) (ociregistry.Descriptor, error) { - return registry.CopyBlob(ctx, *normal.CopyFrom, dstRef) + if dstRef.Tag == "" { + // if we don't have an explicit destination tag, this is considered a request to copy-manifest-from-tag-but-push-by-digest, which is weird, but valid, so we need to copy up that digest into what we look for on the destination side + dstRef.Digest = targetDigest } } - + case typeBlob: + lookupType = registry.LookupTypeBlob + if targetDigest == "" { + // see validation above in normalization + panic("blob ref missing digest, this should never happen: " + dstRef.String()) + } default: panic("unknown type: " + string(normal.Type)) // panic instead of error because this should've already been handled/normalized above (so this is a coding error, not a runtime error) } - return normal, nil + r, err := registry.Lookup(ctx, dstRef, ®istry.LookupOptions{ + Type: lookupType, + Head: true, + }) + if err != nil { + return true, err + } + if r == nil { + // 404! + return true, nil + } + dstDigest := r.Descriptor().Digest + r.Close() + return targetDigest != dstDigest, nil } diff --git a/cmd/deploy/input_test.go b/cmd/deploy/input_test.go index 8304c68..3717c8f 100644 --- a/cmd/deploy/input_test.go +++ b/cmd/deploy/input_test.go @@ -281,7 +281,7 @@ func TestNormalizeInput(t *testing.T) { "refs": [ "localhost:5000/example:test" ], "data": {"mediaType": "application/vnd.oci.image.index.v1+json"} }`, - `{"type":"manifest","refs":["localhost:5000/example:test@sha256:0ae6b7b9d0bc73ee36c1adef005deb431e94cf009c6a947718b31da3d668032d"],"data":"eyJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5pbmRleC52MStqc29uIn0=","copyFrom":null}`, + `{"type":"manifest","refs":["localhost:5000/example:test@sha256:0ae6b7b9d0bc73ee36c1adef005deb431e94cf009c6a947718b31da3d668032d"],"data":"eyJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5pbmRleC52MStqc29uIn0=","mediaType":"application/vnd.oci.image.index.v1+json"}`, }, { "manifest raw", @@ -290,7 +290,7 @@ func TestNormalizeInput(t *testing.T) { "refs": [ "localhost:5000/example" ], "data": "eyJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5pbmRleC52MStqc29uIn0=" }`, - `{"type":"manifest","refs":["localhost:5000/example@sha256:0ae6b7b9d0bc73ee36c1adef005deb431e94cf009c6a947718b31da3d668032d"],"data":"eyJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5pbmRleC52MStqc29uIn0=","copyFrom":null}`, + `{"type":"manifest","refs":["localhost:5000/example@sha256:0ae6b7b9d0bc73ee36c1adef005deb431e94cf009c6a947718b31da3d668032d"],"data":"eyJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5pbmRleC52MStqc29uIn0=","mediaType":"application/vnd.oci.image.index.v1+json"}`, }, { @@ -301,7 +301,7 @@ func TestNormalizeInput(t *testing.T) { "lookup": { "sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d": "tianon/true" }, "data": {"mediaType": "application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d","size":1165}],"schemaVersion":2} }`, - `{"type":"manifest","refs":["localhost:5000/example:test@sha256:0cb474919526d040392883b84e5babb65a149cc605b89b117781ab94e88a5e86"],"lookup":{"sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d":"tianon/true"},"data":"eyJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5pbmRleC52MStqc29uIiwibWFuaWZlc3RzIjpbeyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQub2NpLmltYWdlLm1hbmlmZXN0LnYxK2pzb24iLCJkaWdlc3QiOiJzaGEyNTY6OWVmNDJmMWQ2MDJmYjQyM2ZhZDkzNWFhYzFjYWEwY2ZkYmNlMWFkN2VkY2U2NGQwODBhNGViN2IxM2Y3Y2Q5ZCIsInNpemUiOjExNjV9XSwic2NoZW1hVmVyc2lvbiI6Mn0=","copyFrom":null}`, + `{"type":"manifest","refs":["localhost:5000/example:test@sha256:0cb474919526d040392883b84e5babb65a149cc605b89b117781ab94e88a5e86"],"lookup":{"sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d":"tianon/true"},"data":"eyJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5pbmRleC52MStqc29uIiwibWFuaWZlc3RzIjpbeyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQub2NpLmltYWdlLm1hbmlmZXN0LnYxK2pzb24iLCJkaWdlc3QiOiJzaGEyNTY6OWVmNDJmMWQ2MDJmYjQyM2ZhZDkzNWFhYzFjYWEwY2ZkYmNlMWFkN2VkY2U2NGQwODBhNGViN2IxM2Y3Y2Q5ZCIsInNpemUiOjExNjV9XSwic2NoZW1hVmVyc2lvbiI6Mn0=","mediaType":"application/vnd.oci.image.index.v1+json"}`, }, { "image", @@ -311,7 +311,7 @@ func TestNormalizeInput(t *testing.T) { "lookup": { "": "tianon/true" }, "data": {"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","size":1471,"digest":"sha256:690912094c0165c489f874c72cee4ba208c28992c0699fa6e10d8cc59f93fec9"},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","size":129,"digest":"sha256:4c74d744397d4bcbd3079d9c82a87b80d43da376313772978134d1288f20518c"}]} }`, - `{"type":"manifest","refs":["localhost:5000/example@sha256:1c70f9d471b83100c45d5a218d45bbf7e073e11ea5043758a020379a7c78f878"],"lookup":{"":"tianon/true"},"data":"eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjoxNDcxLCJkaWdlc3QiOiJzaGEyNTY6NjkwOTEyMDk0YzAxNjVjNDg5Zjg3NGM3MmNlZTRiYTIwOGMyODk5MmMwNjk5ZmE2ZTEwZDhjYzU5ZjkzZmVjOSJ9LCJsYXllcnMiOlt7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuaW1hZ2Uucm9vdGZzLmRpZmYudGFyLmd6aXAiLCJzaXplIjoxMjksImRpZ2VzdCI6InNoYTI1Njo0Yzc0ZDc0NDM5N2Q0YmNiZDMwNzlkOWM4MmE4N2I4MGQ0M2RhMzc2MzEzNzcyOTc4MTM0ZDEyODhmMjA1MThjIn1dfQ==","copyFrom":null}`, + `{"type":"manifest","refs":["localhost:5000/example@sha256:1c70f9d471b83100c45d5a218d45bbf7e073e11ea5043758a020379a7c78f878"],"lookup":{"":"tianon/true"},"data":"eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjoxNDcxLCJkaWdlc3QiOiJzaGEyNTY6NjkwOTEyMDk0YzAxNjVjNDg5Zjg3NGM3MmNlZTRiYTIwOGMyODk5MmMwNjk5ZmE2ZTEwZDhjYzU5ZjkzZmVjOSJ9LCJsYXllcnMiOlt7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuaW1hZ2Uucm9vdGZzLmRpZmYudGFyLmd6aXAiLCJzaXplIjoxMjksImRpZ2VzdCI6InNoYTI1Njo0Yzc0ZDc0NDM5N2Q0YmNiZDMwNzlkOWM4MmE4N2I4MGQ0M2RhMzc2MzEzNzcyOTc4MTM0ZDEyODhmMjA1MThjIn1dfQ==","mediaType":"application/vnd.docker.distribution.manifest.v2+json"}`, }, { @@ -321,7 +321,7 @@ func TestNormalizeInput(t *testing.T) { "refs": [ "localhost:5000/example@sha256:1a51828d59323e0e02522c45652b6a7a44a032b464b06d574f067d2358b0e9f1" ], "data": "YnVmZnkgdGhlIHZhbXBpcmUgc2xheWVyCg==" }`, - `{"type":"blob","refs":["localhost:5000/example@sha256:1a51828d59323e0e02522c45652b6a7a44a032b464b06d574f067d2358b0e9f1"],"data":"YnVmZnkgdGhlIHZhbXBpcmUgc2xheWVyCg==","copyFrom":null}`, + `{"type":"blob","refs":["localhost:5000/example@sha256:1a51828d59323e0e02522c45652b6a7a44a032b464b06d574f067d2358b0e9f1"],"data":"YnVmZnkgdGhlIHZhbXBpcmUgc2xheWVyCg=="}`, }, { "blob json", @@ -331,7 +331,7 @@ func TestNormalizeInput(t *testing.T) { "data": { } }`, - `{"type":"blob","refs":["localhost:5000/example@sha256:d914176fd50bd7f565700006a31aa97b79d3ad17cee20c8e5ff2061d5cb74817"],"data":"ewp9Cg==","copyFrom":null}`, + `{"type":"blob","refs":["localhost:5000/example@sha256:d914176fd50bd7f565700006a31aa97b79d3ad17cee20c8e5ff2061d5cb74817"],"data":"ewp9Cg=="}`, }, { @@ -341,7 +341,7 @@ func TestNormalizeInput(t *testing.T) { "refs": [ "localhost:5000/example" ], "lookup": { "sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d": "tianon/true" } }`, - `{"type":"manifest","refs":["localhost:5000/example@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"],"lookup":{"":"tianon/true","sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d":"tianon/true"},"data":null,"copyFrom":"tianon/true@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"}`, + `{"type":"manifest","refs":["localhost:5000/example@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"],"lookup":{"":"tianon/true","sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d":"tianon/true"},"copyFrom":"tianon/true@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"}`, }, { "copy manifest (fallback lookup)", @@ -350,7 +350,7 @@ func TestNormalizeInput(t *testing.T) { "refs": [ "localhost:5000/example" ], "lookup": { "": "tianon/true@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d" } }`, - `{"type":"manifest","refs":["localhost:5000/example@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"],"lookup":{"":"tianon/true"},"data":null,"copyFrom":"tianon/true@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"}`, + `{"type":"manifest","refs":["localhost:5000/example@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"],"lookup":{"":"tianon/true"},"copyFrom":"tianon/true@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"}`, }, { "copy manifest (ref digest+fallback)", @@ -359,7 +359,7 @@ func TestNormalizeInput(t *testing.T) { "refs": [ "localhost:5000/example@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d" ], "lookup": { "": "tianon/true" } }`, - `{"type":"manifest","refs":["localhost:5000/example@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"],"lookup":{"":"tianon/true"},"data":null,"copyFrom":"tianon/true@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"}`, + `{"type":"manifest","refs":["localhost:5000/example@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"],"lookup":{"":"tianon/true"},"copyFrom":"tianon/true@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"}`, }, { "copy manifest (tag)", @@ -368,7 +368,16 @@ func TestNormalizeInput(t *testing.T) { "refs": [ "localhost:5000/example:test" ], "lookup": { "": "tianon/true:oci" } }`, - `{"type":"manifest","refs":["localhost:5000/example:test"],"lookup":{"":"tianon/true"},"data":null,"copyFrom":"tianon/true:oci"}`, + `{"type":"manifest","refs":["localhost:5000/example:test"],"lookup":{"":"tianon/true:oci"},"copyFrom":"tianon/true:oci"}`, + }, + { + "copy manifest (tag and digest)", + `{ + "type": "manifest", + "refs": [ "localhost:5000/example:test" ], + "lookup": { "": "tianon/true:oci@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d" } + }`, + `{"type":"manifest","refs":["localhost:5000/example:test@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"],"lookup":{"":"tianon/true"},"copyFrom":"tianon/true@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"}`, }, { @@ -378,7 +387,7 @@ func TestNormalizeInput(t *testing.T) { "refs": [ "localhost:5000/example" ], "lookup": { "sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e": "tianon/true" } }`, - `{"type":"blob","refs":["localhost:5000/example@sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e"],"lookup":{"":"tianon/true","sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e":"tianon/true"},"data":null,"copyFrom":"tianon/true@sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e"}`, + `{"type":"blob","refs":["localhost:5000/example@sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e"],"lookup":{"":"tianon/true","sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e":"tianon/true"},"copyFrom":"tianon/true@sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e"}`, }, { "copy blob (fallback lookup)", @@ -387,7 +396,7 @@ func TestNormalizeInput(t *testing.T) { "refs": [ "localhost:5000/example" ], "lookup": { "": "tianon/true@sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e" } }`, - `{"type":"blob","refs":["localhost:5000/example@sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e"],"lookup":{"":"tianon/true"},"data":null,"copyFrom":"tianon/true@sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e"}`, + `{"type":"blob","refs":["localhost:5000/example@sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e"],"lookup":{"":"tianon/true"},"copyFrom":"tianon/true@sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e"}`, }, { "copy blob (ref digest+fallback)", @@ -396,7 +405,7 @@ func TestNormalizeInput(t *testing.T) { "refs": [ "localhost:5000/example@sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e" ], "lookup": { "": "tianon/true" } }`, - `{"type":"blob","refs":["localhost:5000/example@sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e"],"lookup":{"":"tianon/true"},"data":null,"copyFrom":"tianon/true@sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e"}`, + `{"type":"blob","refs":["localhost:5000/example@sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e"],"lookup":{"":"tianon/true"},"copyFrom":"tianon/true@sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e"}`, }, { @@ -410,7 +419,7 @@ func TestNormalizeInput(t *testing.T) { ], "lookup": { "": "tianon/true" } }`, - `{"type":"manifest","refs":["localhost:5000/foo@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d","localhost:5000/bar@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d","localhost:5000/baz@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"],"lookup":{"":"tianon/true"},"data":null,"copyFrom":"tianon/true@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"}`, + `{"type":"manifest","refs":["localhost:5000/foo@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d","localhost:5000/bar@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d","localhost:5000/baz@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"],"lookup":{"":"tianon/true"},"copyFrom":"tianon/true@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"}`, }, { "multiple refs + multiple lookup (copy)", @@ -426,24 +435,46 @@ func TestNormalizeInput(t *testing.T) { "sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e": "tianon/true" } }`, - `{"type":"manifest","refs":["localhost:5000/foo@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d","localhost:5000/bar@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d","localhost:5000/baz@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"],"lookup":{"":"tianon/true","sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e":"tianon/true","sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d":"tianon/true"},"data":null,"copyFrom":"tianon/true@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"}`, + `{"type":"manifest","refs":["localhost:5000/foo@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d","localhost:5000/bar@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d","localhost:5000/baz@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"],"lookup":{"":"tianon/true","sha256:25be82253336f0b8c4347bc4ecbbcdc85d0e0f118ccf8dc2e119c0a47a0a486e":"tianon/true","sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d":"tianon/true"},"copyFrom":"tianon/true@sha256:9ef42f1d602fb423fad935aac1caa0cfdbce1ad7edce64d080a4eb7b13f7cd9d"}`, }, } { x := x // https://github.com/golang/go/issues/60078 t.Run(x.name, func(t *testing.T) { - var raw inputRaw - if err := json.Unmarshal([]byte(x.raw), &raw); err != nil { - t.Fatalf("JSON parse error: %v", err) - } - normal, err := NormalizeInput(raw) - if err != nil { - t.Fatalf("normalize error: %v", err) - } - if b, err := json.Marshal(normal); err != nil { - t.Fatalf("JSON generate error: %v", err) - } else if string(b) != x.normal { - t.Fatalf("got:\n%s\n\nexpected:\n%s\n", string(b), x.normal) + var b []byte // this will hold the final "normalized" JSON (so we can test round-trip afterwards) + + { // start a sub-block to ensure variable scoping is clean and roundtrip has to error if there's a typo + var raw inputRaw + if err := json.Unmarshal([]byte(x.raw), &raw); err != nil { + t.Fatalf("JSON parse error: %v", err) + } + normal, err := NormalizeInput(raw) + if err != nil { + t.Fatalf("normalize error: %v", err) + } + b, err = json.Marshal(normal) + if err != nil { + t.Fatalf("JSON generate error: %v", err) + } else if string(b) != x.normal { + t.Fatalf("got:\n%s\n\nexpected:\n%s\n", string(b), x.normal) + } } + + t.Run("roundtrip", func(t *testing.T) { + // now that we've tested that, let's round trip the normalized copy back through the normalizer to make sure it's valid/correctly parsed input too ("deploy --dry-run" leans on that assumption) + var raw inputRaw + if err := json.Unmarshal(b, &raw); err != nil { + t.Fatalf("JSON parse error: %v", err) + } + normal, err := NormalizeInput(raw) + if err != nil { + t.Fatalf("normalize error: %v", err) + } + if roundtripB, err := json.Marshal(normal); err != nil { + t.Fatalf("JSON generate error: %v", err) + } else if string(roundtripB) != x.normal { + t.Fatalf("got:\n%s\n\nexpected:\n%s\n", string(roundtripB), x.normal) + } + }) }) } } diff --git a/cmd/deploy/main.go b/cmd/deploy/main.go index 91862e6..5d0e7f4 100644 --- a/cmd/deploy/main.go +++ b/cmd/deploy/main.go @@ -7,13 +7,44 @@ import ( "os" "os/exec" "os/signal" + "sync" + + "github.com/docker-library/meta-scripts/registry" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) func main() { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() - // TODO --dry-run ? + var ( + args = os.Args[1:] + + // --dry-run + dryRun bool + dryRunOuts chan chan []byte + + // --parallel + parallel bool + ) + for len(args) > 0 { + arg := args[0] + args = args[1:] + + switch arg { + case "--dry-run": + dryRun = true + // we want to allow parallel, but want the output to be in-order so we resynchronize output with a channel of channels (technically this also limits parallelization, but hopefully this limit is generous enough that it doesn't matter) + dryRunOuts = make(chan chan []byte, 1000) + + case "--parallel": + parallel = true + + default: + panic("unknown argument: " + arg) + } + } // TODO the best we can do on whether or not this actually updated tags is "yes, definitely (we had to copy some children)" and "maybe (we didn't have to copy any children)", but we should maybe still output those so we can trigger put-shared based on them (~immediately on "definitely" and with some medium delay on "maybe") @@ -32,6 +63,11 @@ func main() { panic(err) } + // a set of RWMutex objects for synchronizing the pushing of "child" objects before their parents later in the list of documents + // for every RWMutex, it will be *write*-locked during push, and *read*-locked during reading (which means we won't limit the parallelization of multiple parents after a given child is pushed, but we will stop parents from being pushed before their children) + childMutexes := sync.Map{} + wg := sync.WaitGroup{} + dec := json.NewDecoder(stdout) for dec.More() { var raw inputRaw @@ -48,26 +84,173 @@ func main() { } refsDigest := normal.Refs[0].Digest - if normal.CopyFrom == nil { - fmt.Printf("Pushing %s %s:\n", raw.Type, refsDigest) + var logSuffix string = " (" + string(raw.Type) + ") " + if normal.CopyFrom != nil { + // normal copy (one repo/registry to another) + logSuffix = " 🤝" + logSuffix + normal.CopyFrom.String() + // "localhost:32774/test 🤝 (manifest) tianon/test@sha256:4077658bc7e39f02f81d1682fe49f66b3db2c420813e43f5db0c53046167c12f" } else { - fmt.Printf("Copying %s %s:\n", raw.Type, *normal.CopyFrom) + // push (raw/embedded blob or manifest data) + logSuffix = " 🦾" + logSuffix + string(refsDigest) + // "localhost:32774/test 🦾 (blob) sha256:1a51828d59323e0e02522c45652b6a7a44a032b464b06d574f067d2358b0e9f1" } + startedPrefix := "❔ " + successPrefix := "✅ " + failurePrefix := "❌ " + + // locks are per-digest, but refs might be 20 tags on the same digest, so we need to get one write lock per repo@digest and release it when the first tag completes, and every other tag needs a read lock + seenRefs := map[string]bool{} for _, ref := range normal.Refs { - fmt.Printf(" - %s", ref.StringWithKnownDigest(refsDigest)) - desc, err := normal.Do(ctx, ref) - if err != nil { - fmt.Fprintf(os.Stderr, " -- ERROR: %v\n", err) - os.Exit(1) - return + ref := ref // https://github.com/golang/go/issues/60078 + + necessaryReadLockRefs := []registry.Reference{} + + // before parallelization, collect the pushing "child" mutex we need to lock for writing right away (but only for the first entry) + var mutex *sync.RWMutex + if ref.Digest != "" { + lockRef := ref + lockRef.Tag = "" + lockRefStr := lockRef.String() + if seenRefs[lockRefStr] { + // if we've already seen this specific ref for this input, we need a read lock, not a write lock (since they're per-repo@digest) + necessaryReadLockRefs = append(necessaryReadLockRefs, lockRef) + } else { + seenRefs[lockRefStr] = true + lock, _ := childMutexes.LoadOrStore(lockRefStr, &sync.RWMutex{}) + mutex = lock.(*sync.RWMutex) + // if we have a "child" mutex, lock it immediately so we don't create a race between inputs + mutex.Lock() // (this gets unlocked in the goroutine below) + // this is sane to lock here because interdependent inputs are required to be in-order (children first), so if this hangs it's 100% a bug in the input order + } } - if ref.Digest == "" && refsDigest == "" { - fmt.Printf("@%s", desc.Digest) + + // make a (deep) copy of "normal" so that we can use it in a goroutine ("normal.do" is not safe for concurrent invocation) + normal := normal.clone() + + var dryRunOut chan []byte + if dryRun { + dryRunOut = make(chan []byte, 1) + dryRunOuts <- dryRunOut + } + + wg.Add(1) + // (making a function instead of direct "go func() ..." so we can support the --parallel toggle) + f := func() { + defer wg.Done() + + if mutex != nil { + defer mutex.Unlock() + } + + if dryRun { + defer close(dryRunOut) + } + + // before we start this job (parallelized), if it's a raw data job we need to parse the raw data and see if any of the "children" are objects we're still in the process of pushing (from a previously parallel job) + if len(normal.Data) > 2 { // needs to at least be bigger than "{}" for us to care (anything else either doesn't have data or can't have children) + // explicitly ignoring errors because this might not actually be JSON (or even a manifest at all!); this is best-effort + // TODO optimize this by checking whether normal.Data matches "^\s*{.+}\s*$" first so we have some assurance it might work before we go further? + manifestChildren, _ := registry.ParseManifestChildren(normal.Data) + childDescs := []ocispec.Descriptor{} + childDescs = append(childDescs, manifestChildren.Manifests...) + if manifestChildren.Config != nil { + childDescs = append(childDescs, *manifestChildren.Config) + } + childDescs = append(childDescs, manifestChildren.Layers...) + for _, childDesc := range childDescs { + childRef := ref + childRef.Digest = childDesc.Digest + necessaryReadLockRefs = append(necessaryReadLockRefs, childRef) + + // these read locks are cheap, so let's be aggressive with our "lookup" refs too + if lookupRef, ok := normal.Lookup[childDesc.Digest]; ok { + lookupRef.Digest = childDesc.Digest + necessaryReadLockRefs = append(necessaryReadLockRefs, lookupRef) + } + if fallbackRef, ok := normal.Lookup[""]; ok { + fallbackRef.Digest = childDesc.Digest + necessaryReadLockRefs = append(necessaryReadLockRefs, fallbackRef) + } + } + } + // we don't *know* that all the lookup references are children, but if any of them have an explicit digest, let's treat them as potential children too (which is fair, because they *are* explicit potential references that it's sane to make sure exist) + for digest, lookupRef := range normal.Lookup { + necessaryReadLockRefs = append(necessaryReadLockRefs, lookupRef) + if digest != lookupRef.Digest { + lookupRef.Digest = digest + necessaryReadLockRefs = append(necessaryReadLockRefs, lookupRef) + } + } + // if we're going to do a copy, we need to *also* include the artifact we're copying in our list + if normal.CopyFrom != nil { + necessaryReadLockRefs = append(necessaryReadLockRefs, *normal.CopyFrom) + } + // ok, we've built up a list, let's start grabbing (ro) mutexes + seenChildren := map[string]bool{} + for _, lockRef := range necessaryReadLockRefs { + lockRef.Tag = "" + if lockRef.Digest == "" { + continue + } + lockRefStr := lockRef.String() + if seenChildren[lockRefStr] { + continue + } + seenChildren[lockRefStr] = true + lock, _ := childMutexes.LoadOrStore(lockRefStr, &sync.RWMutex{}) + lock.(*sync.RWMutex).RLock() + defer lock.(*sync.RWMutex).RUnlock() + } + + if dryRun { + needsDeploy, err := normal.dryRun(ctx, ref) + if err != nil { + fmt.Fprintf(os.Stderr, "%s -- ERROR: %v\n", failurePrefix+ref.String()+logSuffix, err) + panic(err) // TODO exit in a more clean way (we can't use "os.Exit" because that causes *more* errors 😭) + } + if needsDeploy { + normal.Refs = []registry.Reference{ref} + j, err := json.MarshalIndent(normal, "", "\t") + if err != nil { + fmt.Fprintf(os.Stderr, "%s -- JSON ERROR: %v\n", failurePrefix+ref.String()+logSuffix, err) + panic(err) // TODO exit in a more clean way (we can't use "os.Exit" because that causes *more* errors 😭) + } + dryRunOut <- j + } + } else { + logText := ref.StringWithKnownDigest(refsDigest) + logSuffix + fmt.Println(startedPrefix + logText) + desc, err := normal.do(ctx, ref) + if err != nil { + fmt.Fprintf(os.Stderr, "%s%s -- ERROR: %v\n", failurePrefix, logText, err) + panic(err) // TODO exit in a more clean way (we can't use "os.Exit" because that causes *more* errors 😭) + } + if ref.Digest == "" && refsDigest == "" { + logText += "@" + string(desc.Digest) + } + fmt.Println(successPrefix + logText) + } + } + if parallel { + go f() + } else { + f() } - fmt.Println() } + } - fmt.Println() + if dryRun { + close(dryRunOuts) + for dryRunOut := range dryRunOuts { + j, ok := <-dryRunOut + if !ok { + // (I think) this means we didn't output anything, so this should be all our "skips" + continue + } + fmt.Printf("%s\n", j) + } } + + wg.Wait() } diff --git a/registry/manifest-children.go b/registry/manifest-children.go new file mode 100644 index 0000000..15e4c2a --- /dev/null +++ b/registry/manifest-children.go @@ -0,0 +1,25 @@ +package registry + +import ( + "encoding/json" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + +type ManifestChildren struct { + // *technically* this should be two separate structs chosen based on mediaType (https://github.com/opencontainers/distribution-spec/security/advisories/GHSA-mc8v-mgrf-8f4m), but that makes the code a lot more annoying when we're just collecting a list of potential children we need to copy over for the parent object to push successfully + + // intentional subset of https://github.com/opencontainers/image-spec/blob/v1.1.0/specs-go/v1/index.go#L21 to minimize parsing + Manifests []ocispec.Descriptor `json:"manifests"` + + // intentional subset of https://github.com/opencontainers/image-spec/blob/v1.1.0/specs-go/v1/manifest.go#L20 to minimize parsing + Config *ocispec.Descriptor `json:"config"` // have to turn this into a pointer so we can recognize when it's not set easier / more correctly + Layers []ocispec.Descriptor `json:"layers"` +} + +// opportunistically parse a given manifest for any *potential* child objects; will return JSON parsing errors for non-JSON +func ParseManifestChildren(manifest []byte) (ManifestChildren, error) { + var manifestChildren ManifestChildren + err := json.Unmarshal(manifest, &manifestChildren) + return manifestChildren, err +} diff --git a/registry/push.go b/registry/push.go index 9075efb..e5309f7 100644 --- a/registry/push.go +++ b/registry/push.go @@ -74,17 +74,8 @@ func EnsureManifest(ctx context.Context, ref Reference, manifest json.RawMessage errors.Is(err, ociregistry.ErrBlobUnknown) || (errors.As(err, &httpErr) && httpErr.StatusCode() >= 400 && httpErr.StatusCode() <= 499) { // this probably means we need to push some child manifests and/or mount missing blobs (and then retry the manifest push) - var manifestChildren struct { - // *technically* this should be two separate structs chosen based on mediaType (https://github.com/opencontainers/distribution-spec/security/advisories/GHSA-mc8v-mgrf-8f4m), but that makes the code a lot more annoying when we're just collecting a list of potential children we need to copy over for the parent object to push successfully - - // intentional subset of https://github.com/opencontainers/image-spec/blob/v1.1.0/specs-go/v1/index.go#L21 to minimize parsing - Manifests []ocispec.Descriptor `json:"manifests"` - - // intentional subset of https://github.com/opencontainers/image-spec/blob/v1.1.0/specs-go/v1/manifest.go#L20 to minimize parsing - Config *ocispec.Descriptor `json:"config"` // have to turn this into a pointer so we can recognize when it's not set easier / more correctly - Layers []ocispec.Descriptor `json:"layers"` - } - if err := json.Unmarshal(manifest, &manifestChildren); err != nil { + manifestChildren, err := ParseManifestChildren(manifest) + if err != nil { return desc, fmt.Errorf("%s: failed parsing manifest JSON: %w", ref, err) }