Closed vicSenior closed 4 months ago
@vicSenior Thank you for getting to the bottom of this.
Have you tried adding the following to Spring Cloud Data Flow Server configuration:
spring:
cloud:
dataflow:
container:
registry-configurations:
qauy-private:
registry-host: quay-registry.local
manifest-media-type: application/vnd.oci.image.manifest.v1+json
.... other properties
The lookup of the container configuration is done by matching the registry-host to the host in the image uri.
@corneil thank you. But your proposal doesn't help: in the registry 'quay-registry.local' I have mixed images (some with manifest mediaType 'docker', others with 'oci').
@vicSenior We will look into implementing your proposal.
@vicSenior Have you checked the output of v2/{repository}/blobs/{digest}
for both types of containers? Are they the same or does the accepts also affect those?
Hello, @corneil, I've performed tests that you has required.
Inspecting the source code, the method getImageBlob()
(line 219 in file ContainerRegistryService.java
) not defined the ACCEPT:
header for the Rest http GET call (see tests [b] below).
Anyway, I've performed tests for all cases (I hope this is enough to dispel any doubts):
[ ] image with mediaType manifest OCI
:
a) curl URL v2/{repository}/manifests/{reference}
Accept:application/vnd.docker.distribution.manifest.v2+json
-> [.schemaVersion] 1
-> [.config] null
-> [.config.digest] nullAccept:application/vnd.oci.image.manifest.v1+json
-> [.schemaVersion] 2
-> [.config] exist
-> [.config.digest] exist ... obviously :)
b) curl URL v2/{repository}/blobs/{digest}
Accept:application/vnd.docker.distribution.manifest.v2+json
-> obtained json (35702 bytes) with md5sum ee5c63007a8c32cb79d182d16c2b7362
Accept:application/vnd.oci.image.manifest.v1+json
-> obtained json (35702 bytes) with md5sum ee5c63007a8c32cb79d182d16c2b7362
Accept:application/json
-> obtained json (35702 bytes) with md5sum ee5c63007a8c32cb79d182d16c2b7362
Accept:
-> obtained json (35702 bytes) with md5sum ee5c63007a8c32cb79d182d16c2b7362
[ ] image with mediaType manifest Docker
:
a) curl URL v2/{repository}/manifests/{reference}
1) with Header Accept:application/vnd.docker.distribution.manifest.v2+json
-> [.schemaVersion] 2
-> [.config] exist
-> [.config.digest] exist ... obviously :)
2) with Header Accept:application/vnd.oci.image.manifest.v1+json
-> [.schemaVersion] 1
-> [.config] null
-> [.config.digest] null
b) curl URL v2/{repository}/blobs/{digest}
Accept:application/vnd.docker.distribution.manifest.v2+json
-> obtained json (40240 bytes) with md5sum ae888dddec6b36f36231e59363912057
Accept:application/vnd.oci.image.manifest.v1+json
-> obtained json (40240 bytes) with md5sum ae888dddec6b36f36231e59363912057
Accept:application/json
-> obtained json (40240 bytes) with md5sum ae888dddec6b36f36231e59363912057
Accept:
-> obtained json (40240 bytes) with md5sum ae888dddec6b36f36231e59363912057
So, for all [b] tests (curl v2/{repository}/blobs/{digest}
) i've obtain a json, with same size and same md5sum sign.
If you need anything else, I'm here. Thank you!
I perceive that the use of the schemaVersion
key is disliked (for reasons of future potential disuse).
I thought of an alternative way to get the .config
object from the Manifest without using schemaVersion
key.
Let's consider the case where SCDF needs to retrieve configurations from three images of a pipe img1 | img2 | img3
And suppose that:
For example:
public static final String CUSTOM_IMAGE_MANIFEST_MEDIA_TYPE = "application/vnd.custom.test1.manifest.v3+json";
public static final String FUTURE_IMAGE_MANIFEST_MEDIA_TYPE = "application/vnd.future.img.manifest.v1+json";
public static final String OCI_IMAGE_MANIFEST_MEDIA_TYPE = "application/vnd.oci.image.manifest.v1+json";
public static final String DOCKER_IMAGE_MANIFEST_MEDIA_TYPE = "application/vnd.docker.distribution.manifest.v2+json";
So, if we wanted to retrieve the .config
object from a Manifest without knowing the version of the schemaVersion
, we could:
v2/{repository}/manifests/{reference}
using a header Accept: application/json
(generic) ContentType: application/X-Y-Z
, with X-Y-Z
the specific mediaType used by the Publisher to package the Docker image Manifest in the registry.Accept:
of request and the ContentType:
of response) are the same, we can extract the .config
object, because it is present.Accept:
and response's ContentType:
) are different, we will need to make a second call to the URL v2/{repository}/manifests/{reference}
using a Header Accept:
with the right value obtained from the ContentType:
response Header (in the example X-Y-Z
). This way the second response will certainly be equipped with the .config
object.This algorithm requires at most two calls to retrieve the .config.
object from the Manifest.
You can program the first attempt using the Docker standard as the Accept:
header (application/vnd.docker.distribution.manifest.v2+json
), which in most cases is sufficient to obtain the .config
object on the first try.
If, however, SCDF needs to query for an image with a Manifest mediaType different from Docker
(or any other standard, even to be defined in the future), a second (and final) call will be enough to retrieve .config
object.
Example A:
vnd.docker...
:
Accept: application/vnd.docker.distribution.manifest.v2+json
)
.config
foundvnd.oci...
:
Accept: application/vnd.docker.distribution.manifest.v2+json
)
.config
null ContentType: application/vnd.oci.image.manifest.v1+json
Accept: application/vnd.oci.image.manifest.v1+json
)
.config
foundvnd.docker...
:
Accept: application/vnd.docker.distribution.manifest.v2+json
)
.config
foundso with total of 4 Rest http GET we got all the .config
objects for every Images of pipe.
Example B (worst-case):
vnd.custom...
:
Accept: application/vnd.docker.distribution.manifest.v2+json
)
.config
null ContentType: application/vnd.custom.test1.manifest.v3+json
Accept: application/vnd.custom.test1.manifest.v3+json
)
.config
foundvnd.oci...
:
Accept: application/vnd.docker.distribution.manifest.v2+json
)
.config
null ContentType: application/vnd.oci.image.manifest.v1+json
Accept: application/vnd.oci.image.manifest.v1+json
)
.config
foundvnd.future...
:
Accept: application/vnd.docker.distribution.manifest.v2+json
)
.config
null ContentType: application/vnd.future.img.manifest.v1+json
Accept: application/vnd.future.img.manifest.v1+json
)
.config
foundso with total of 6 Rest http GET (n*2
, with n
number of Images that compose the pipe) we got all the .config
objects for every Images of pipe.
In method getImageManifest()
of Class ContainerRegistryService
(file [ContainerRegistryService.java
]) we can use (after line 220) the manifest
object, that provide the 'ContentType' response Header with instruction:
manifest.getHeaders().getContentType().toString();
with .getContentType()
that return a MediaType
object.
Resolved with PR #5823 @vicSenior Thank you for raising this issue!
Description:
My purpose is to create SCDF Streams of Applications that make use of customized Docker images. All custom Docker images have been customized with application properties defined in the json
org.springframework.cloud.dataflow.spring-configuration-metadata.json
, declared with LABEL in DockerFile.So, suppose that a Stream of Applications was declared as
http | my-custom-app | log
pipe. In detail:http
point to official SCDF imagedocker:springcloudstream/http-source-kafka:3.2.1
my-custom-app
point to image stored in Private Registrydocker:my-quay-registry.local/poc-scdf/my-custom-app:0.0.1
log
point to official SCDF imagedocker:springcloudstream/log-sink-kafka:3.2.1
The problem arises when SCDF tries to collect
.config
information frommy-custom-app
manifest, becausemy-custom-app
has been published (using Azure DevOps automation) in Quay private Registry using OCI Standard (so mediaType manifest isvnd.oci.image.manifest.v1+json
).In fact, looking at the source code, I understood that Rest http GET calls are performed using (always) the header
Accept:application/vnd.docker.container.image.v1+json
. That call, performed for an image published with Standard OCI, returns a manifest withSchemaVersion=1
(that is devoid of the.config
object).Follow the smoke trail:
The exception
ContainerRegistryException
reported by the SCDFserver
component is the following:which is raised on line 55 of the
DefaultContainerImageMetadataResolver.java
file of methodgetImageLabels(String imageName)
:Let's evaluate the two conditions:
manifest != null
] is always true (because we can see the manifest object stringified in the exception message, by.toString()
method),!isNotNullMap(manifest.get("config"))
] is therefore the cause of the problem, withmanifest.get("config")
a null object.Additionally, the Rest http GET used to recovery the informations that define the
manifest
object is in methodgetImageManifest()
of fileContainerRegistryService.java
, and we can see that (on line 203) were used che Header Accept with value of StringimageManifestMediaType
:The String
imageManifestMediaType
receive the value fromregistryRequest.getRegistryConf().getManifestMediaType()
(line 198 in fileContainerRegistryRequest.java
). So,registryRequest.getRegistryConf()
return an object of ClassContainerRegistryConfiguration.java
and method.getManifestMediaType()
return the StringmanifestMediaType
with constant value of StringDOCKER_IMAGE_MANIFEST_MEDIA_TYPE
declared in fileContainerRegistryProperties.java
:In conclusion, SCDF performs the recovery of the
manifest
by declaring (always) the headerAccept:application/vnd.docker.distribution.manifest.v2+json
in the Rest http GET: this is not good for containers created with the "OCI" standard. In fact, that retrieve amanifest
withSchemaVersion=1
, without the.config
object.Steps to reproduce the issue:
Using CURL command, with the specific
Accept:
header, we can retrieve manifest of a docker image:Accept:application/vnd.docker.distribution.manifest.v2+json
:Command:
Command:
Focus on the structure of the image manifests that compose my Stream Applications:
As mentioned above, taht is the structure of the pipe that makes up my Stream Applications:
http | my-custom-app | log
. In detail:http
point to official SCDF imagedocker:springcloudstream/http-source-kafka:3.2.1
, with manifest mediaTypeapplication/vnd.docker.distribution.manifest.v2+json
my-custom-app
point to image stored in Private Registrydocker:my-quay-registry.local/poc-scdf/my-custom-app:0.0.1
, with manifest mediaTypeapplication/vnd.oci.image.manifest.v1+json
log
point to official SCDF imagedocker:springcloudstream/log-sink-kafka:3.2.1
, with manifest mediaTypeapplication/vnd.docker.distribution.manifest.v2+json
Proposal for solving the problem
Modify the method
getImageLabels(String imageName)
(frame of code from line 53 to 57, in fileDefaultContainerImageMetadataResolver.java
) in order to check if manifest obtained hasschemaVersion=1
or=2
. ifschemaVersion=1
it may be useful to make a second attempt, by changing theAccept:
header of the Rest http GET (and using the OCI one already prepared with the StringOCI_IMAGE_MANIFEST_MEDIA_TYPE
on line 33 in fileContainerRegistryProperties.java
.Release versions:
I'm using SCDF version 2.11.2 installed in a Kubernetes environment using Helm.