We had 2 bags of grass, 75 tablets of mescaline unix environment, docker repository and task to implement docker pull and docker push commands without docker client.

UPD:
Question: What is all this for?
Answer: Load testing of the product (NOT by means of bash, scripts are provided for educational purposes). It was decided not to use the docker client to reduce additional layers (within reasonable limits) and, accordingly, emulate a higher load. As a result, all system delays of the docker client were removed. Received a relatively clean load directly on the product
First, let's see what these teams do.
So what is docker pull used for? According to the documentation :
"Pull an image or a repository from a registry".
There we find a link to understand images, containers, and storage drivers .

docker image layers, , . registry API.
:
"An “image” is a combination of a JSON manifest and individual layer files. The process of pulling an > image centers around retrieving these two components."
“Pulling an Image Manifest”.
, . : GET /v2/{name}/manifests/{reference}
"The name and reference parameter identify the image and are required. The reference may include a tag or digest."
, :
curl -s -X GET "http://localhost:8081/link/to/docker/registry/v2/centos-11-10/manifests/1.1.1" -H "header_if_needed"

json , . , : "GET /v2/{name}/blobs/{digest}"
“Access to a layer will be gated by the name of the repository but is identified uniquely in the registry by digest.”
digest , .
curl -s -X GET "http://localhost:8081/link/to/docker/registry/v2/centos-11-10/blobs/sha256:f972d139738dfcd1519fd2461815651336ee25a8b54c358834c50af094bb262f" -H "header_if_needed" --output firstLayer

.
file firstLayer

.. tar , .
downloadDir=$1
url=$2
imageName=$3
tag=$4
layers=($(curl -s -X GET "$url/v2/$imageName/manifests/$tag" | grep -oP '(?<=blobSum" : ").+(?=")'))
for layer in "${layers[@]}"; do
echo "Downloading ${layer}"
curl -v -X GET "$url/v2/$imageName/blobs/$layer" --output "$downloadDir/$layer.tar"
done
cd "$downloadDir" && find . -name "sha256:*" -exec tar xvf {} \;
rm sha256:*.tar
exit 0
./script.sh dirName “http://localhost:8081/link/to/docker/registry” myAwesomeImage 1.0
2 — docker push
.
. , . .
:
- — "POST /v2/{repoName}/blobs/uploads/"
- ( , .. ) — "PUT /v2/{repoName}/blobs/uploads/{uuid}?digest={digest}
Content-Length: {size of layer}
Content-Type: application/octet-stream
Layer Binary Data". - — "PUT /v2/{repoName}/manifests/{reference}".
, . (chunked) PATCH :
"PATCH /v2/{repoName}/blobs/uploads/{uuid}
Content-Length: {size of chunk}
Content-Type: application/octet-stream
{Layer Chunk Binary Data}".
, .. 202 4.
:
. archlinux:latest
docker pull archlinux

docker save c24fe13d37b9 -o savedArch

tar xvf savedArch

. ,
cat manifest.json | json_pp

. , .

, .
config . 2 ( ), mediaType :
echo ‘{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": config_size,
"digest": "config_hash"
},
"layers": [
’ > manifest.json
. json :
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": ${layersSizes[$i]},
"digest": \"sha256:${layersNames[$i]}\"
},
.
sed -i "s/config_size/$configSize/g; s/config_hash/$configName/g" $manifestFile
Now you can initiate the boot process and save yourself uuid, which should be accompanied by all subsequent requests.
The full script looks something like this:
imageDir=$1
url=$2
repoName=$3
tag=$4
manifestFile=$(readlink -f ${imageDir}/manifestCopy)
configFile=$(readlink -f $(find $imageDir -name "*.json" ! -name "manifest.json"))
function prepareLayersForUpload() {
info_file=$imageDir/info
layersNames=($(find $imageDir -name "layer.tar" -exec shasum -a 256 {} \; | cut -d" " -f1))
find $imageDir -name "layer.tar" -exec bash -c 'mv {} "$(echo {} | cut -d"/" -f1,2)/$(shasum -a 256 {} | cut -d" " -f1)"' \;
layersSizes=($(find $imageDir -name "*.tar" -exec ls -l {} \; | awk '{print $5}'))
for i in "${!layersNames[@]}"; do
echo "{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": ${layersSizes[$i]},
"digest": \"sha256:${layersNames[$i]}\"
}," >> $manifestFile
done
truncate -s-2 $manifestFile
printf "\n\t]\n}" >> $manifestFile
}
function setConfigProps() {
configSize=$(ls -l $configFile | awk '{print $5}')
configName=$(basename $configFile | cut -d"." -f1)
sed -i "s/config_size/$configSize/g; s/config_hash/$configName/g" $manifestFile
}
prepareLayersForUpload
setConfigProps
cat $manifestFile
uuid=$(curl -s -X POST -I "$url/v2/$repoName/blobs/uploads/" | grep -oP "(?<=Docker-Upload-Uuid: ).+")
for l in "${!layersNames[@]}"; do
pathToLayer=$(find $imageDir -name ${layersNames[$l]} -exec readlink -f {} \;)
curl -v -X PATCH "$url/v2/$repoName/blobs/uploads/$uuid" \
-H "Content-Length: ${layersSizes[$i]}" \
-H "Content-Type: application/octet-stream" \
--data-binary "@$pathToLayer"
curl -v -X PUT "$url/v2/$repoName/blobs/uploads/$uuid?digest=sha256:${layersNames[$i]}" \
-H 'Content-Type: application/octet-stream' \
-H "Content-Length: ${layersSizes[$i]}" \
--data-binary "@$pathToLayer"
done
curl -v -X PATCH "$url/v2/$repoName/blobs/uploads/$uuid" \
-H "Content-Length: $configSize" \
-H "Content-Type: application/octet-stream" \
--data-binary "@$configFile"
curl -v -X PUT "$url/v2/$repoName/blobs/uploads/$uuid?digest=sha256:$configName" \
-H 'Content-Type: application/octet-stream' \
-H "Content-Length: $configSize" \
--data-binary "@$configFile"
curl -v -X PUT "$url/v2/$repoName/manifests/$tag" \
-H 'Content-Type: application/vnd.docker.distribution.manifest.v2+json' \
--data-binary "@$manifestFile"
exit 0
we can use a ready-made script:
./uploadImage.sh "~/path/to/saved/image" "http://localhost:8081/link/to/docker/registry" myRepoName 1.0
The article used GNU version tools.
In general, the article does not discover America, but slightly structures the disparate data and supplements the gaps in the documentation. Thank you for reading.