Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serialization Error When Loading Multi-Document YAML in fabric8 Kubernetes Client #6009

Closed
fengyinqiao opened this issue May 15, 2024 · 8 comments

Comments

@fengyinqiao
Copy link

Describe the bug

Encountering a deserialization error when attempting to use the Serialization.unmarshal method to handle a YAML string containing multiple Kubernetes resource definitions separated by ---, and trying to deserialize it as List.class.

Error Details:

java.lang.IllegalArgumentException: Cannot deserialize value of type `java.util.ArrayList<java.lang.Object>` from Object value (token `JsonToken.START_OBJECT`)
 at [Source: UNKNOWN; line: -1, column: -1]

Fabric8 Kubernetes Client version

6.9.2

Steps to reproduce

  1. Prepare a YAML string containing multiple resource definitions separated by ---.
  2. Attempt to load and deserialize the YAML string using the following code:
InputStream is = new ByteArrayInputStream(ymlContent.getBytes(StandardCharsets.UTF_8));
List<HasMetadata> resources = Serialization.unmarshal(is, List.class);

Actual Behavior:
Throws a type mismatch exception when the YAML definitions contain only a single resource object instead of an array.

Expected behavior

The method should correctly parse the multi-document YAML string and deserialize each definition into a corresponding list of Kubernetes resource objects.

Runtime

Kubernetes (vanilla)

Kubernetes API Server version

1.23

Environment

macOS

Fabric8 Kubernetes Client Logs

No response

Additional context

Consider enhancing the Serialization.unmarshal method to more intelligently handle both single objects and arrays of objects, or clarify in the documentation how such cases should be handled.

@rohanKanojia
Copy link
Member

@fengyinqiao : Hello, we have a test for verifying this multi-line document YAML deserialization. Could you please check if your YAML is similar to this?

---
apiVersion: v1
kind: List
items:

This yaml gets used in KubernetesClientImplTest

@fengyinqiao
Copy link
Author

fengyinqiao commented May 15, 2024

@fengyinqiao : Hello, we have a test for verifying this multi-line document YAML deserialization. Could you please check if your YAML is similar to this?

---
apiVersion: v1
kind: List
items:

This yaml gets used in KubernetesClientImplTest

@rohanKanojia Really thank's for your reply! my YAML is as below:

 apiVersion:  elasticsearch.k8s.elastic.co/v1
 kind: Elasticsearch 
---
 apiVersion: kibana.k8s.elastic.co/v1
 kind: Kibana 

@rohanKanojia
Copy link
Member

@fengyinqiao : When I copy paste your YAML and add this test, it seems to pass.

  @Test
  void multiline() {
    // Given
    // When
    try (KubernetesClient kubernetesClient = new KubernetesClientBuilder().build()) {
      List<HasMetadata> result = kubernetesClient.load(KubernetesClientImplTest.class.getResourceAsStream("/multi-line.yml")).items();
      // Then
      assertThat(result)
        .hasSize(2)
        .hasAtLeastOneElementOfType(GenericKubernetesResource.class)
        .hasAtLeastOneElementOfType(GenericKubernetesResource.class);
    }
  }

@fengyinqiao
Copy link
Author

@fengyinqiao : When I copy paste your YAML and add this test, it seems to pass.

  @Test
  void multiline() {
    // Given
    // When
    try (KubernetesClient kubernetesClient = new KubernetesClientBuilder().build()) {
      List<HasMetadata> result = kubernetesClient.load(KubernetesClientImplTest.class.getResourceAsStream("/multi-line.yml")).items();
      // Then
      assertThat(result)
        .hasSize(2)
        .hasAtLeastOneElementOfType(GenericKubernetesResource.class)
        .hasAtLeastOneElementOfType(GenericKubernetesResource.class);
    }
  }

@rohanKanojia Thanks for your code. It does work. And I find another way based on my code just now, it also works:

List<HasMetadata> resources = new ArrayList<>();
try (InputStream is = new ByteArrayInputStream(ymlContent.getBytes(StandardCharsets.UTF_8))) {
            resources.addAll(Serialization.unmarshal(is));
            assertThat(resources)
            .hasSize(2)
            .hasAtLeastOneElementOfType(GenericKubernetesResource.class)
            .hasAtLeastOneElementOfType(GenericKubernetesResource.class);
}

so, why my original code is wrong?

@rohanKanojia
Copy link
Member

so, why my original code is wrong?

Are you using java.util.List in your initial method? Does it work if you use io.fabric8.kubernetes.api.model.KubernetesResourceList ?

@fengyinqiao
Copy link
Author

Does it work if you use io.fabric8.kubernetes.api.model.KubernetesResourceList ?

@rohanKanojia I get null , use below code:

try (InputStream is = new ByteArrayInputStream(ymlContent.getBytes(StandardCharsets.UTF_8))) {
            KubernetesResourceList kubernetesResourceList = Serialization.unmarshal(is);
            assertNull(kubernetesResourceList)
}

@rohanKanojia
Copy link
Member

rohanKanojia commented Jun 4, 2024

@fengyinqiao : Sorry for late reply, I tried your sample but I'm getting a different error:

Exception in thread "main" java.lang.ClassCastException: class java.util.ArrayList cannot be cast to class io.fabric8.kubernetes.api.model.KubernetesResourceList (java.util.ArrayList is in module java.base of loader 'bootstrap'; io.fabric8.kubernetes.api.model.KubernetesResourceList is in unnamed module of loader 'app')
	at io.fabric8.ClientLoadYamlWithMultipleDocumentDelimiters.main(ClientLoadYamlWithMultipleDocumentDelimiters.java:12)

Serialization.unmarshal is returning a List<HasMetadata> and it's giving a cast error as List cannot be cast to KubernetesResourceList

if I change code like this it start working :

try (InputStream is = new ByteArrayInputStream(ymlContent.getBytes(StandardCharsets.UTF_8))) {
            List<HasMetadata> kubernetesResourceList = Serialization.unmarshal(is);
            assertNull(kubernetesResourceList)
}

@fengyinqiao
Copy link
Author

fengyinqiao commented Jun 8, 2024

@fengyinqiao : Sorry for late reply, I tried your sample but I'm getting a different error:

Exception in thread "main" java.lang.ClassCastException: class java.util.ArrayList cannot be cast to class io.fabric8.kubernetes.api.model.KubernetesResourceList (java.util.ArrayList is in module java.base of loader 'bootstrap'; io.fabric8.kubernetes.api.model.KubernetesResourceList is in unnamed module of loader 'app')
	at io.fabric8.ClientLoadYamlWithMultipleDocumentDelimiters.main(ClientLoadYamlWithMultipleDocumentDelimiters.java:12)

Serialization.unmarshal is returning a List<HasMetadata> and it's giving a cast error as List cannot be cast to KubernetesResourceList

if I change code like this it start working :

try (InputStream is = new ByteArrayInputStream(ymlContent.getBytes(StandardCharsets.UTF_8))) {
            List<HasMetadata> kubernetesResourceList = Serialization.unmarshal(is);
            assertNull(kubernetesResourceList)
}

@rohanKanojia Thanks for your time and reply, I tried the code snippet below later, and it works.

 try (InputStream is = new ByteArrayInputStream(ymlContent.getBytes(StandardCharsets.UTF_8))) {
        List<HasMetadata> resources = client.load(is).items();
        ...
 }

Serialization.unmarshal() is deprecated, I already gave up use it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants