Repository: spinnaker/spinnaker
Pull request #7077: fix(web): fix HelmConverterFactory for deserialization error
Author: login: kirangodishala
Labels: name: ready to merge
description: Approved and ready for merge
name: auto merged
name: target-release/2025.1
Created: 2025-05-16T18:33:21Z | Merged: 2025-05-17T06:01:56Z

- When Igor was trying to call Clouddriver to fetch helm accounts, presence of additional field(s) was causing SpinnakerConvertionException:
```
INFO 2025-05-15T23:30:38.603945402Z [resource.labels.containerName: igor] 2025-05-15 23:30:38.603 ERROR 1 --- [ scheduling-1] c.n.s.igor.helm.accounts.HelmAccounts : Failed to get list of Helm accounts
com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerConversionException: Failed to process response body: Unrecognized field "type" (class com.netflix.spinnaker.igor.helm.accounts.ArtifactAccount), not marked as ignorable (2 known properties: "types", "name"])
 at [Source: (okhttp3.ResponseBody$BomAwareReader); line: 1, column: 38] (through reference chain: java.util.ArrayList[0]->com.netflix.spinnaker.igor.helm.accounts.ArtifactAccount["type"])
	at com.netflix.spinnaker.kork.retrofit.ErrorHandlingExecutorCallAdapterFactory$ExecutorCallbackCall.execute(ErrorHandlingExecutorCallAdapterFactory.java:156) ~[kork-retrofit-7.254.0.jar:7.254.0]
	at com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall.executeCall(Retrofit2SyncCall.java:47) ~[kork-retrofit-7.254.0.jar:7.254.0]
	at com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall.execute(Retrofit2SyncCall.java:34) ~[kork-retrofit-7.254.0.jar:7.254.0]
	at com.netflix.spinnaker.igor.helm.accounts.HelmAccounts.updateAccounts(HelmAccounts.java:62) ~[igor-web-4.22.0.jar:4.22.0]
```

- This PR addresses the issue by configuring the object mapper within HelmConverterFactory to ignore the unknown fields while deserializing. 
- Added a test to demonstrate the issue.



Diff:
diff --git a/igor/igor-web/igor-web.gradle b/igor/igor-web/igor-web.gradle
index 87227818a7d..c6e0055ae98 100644
--- a/igor/igor-web/igor-web.gradle
+++ b/igor/igor-web/igor-web.gradle
@@ -79,6 +79,7 @@ dependencies {
 
     testImplementation "com.squareup.okhttp3:mockwebserver"
     testImplementation "io.spinnaker.kork:kork-jedis-test"
+    testImplementation "io.spinnaker.kork:kork-retrofit"
     testImplementation "org.apache.groovy:groovy-datetime"
     testImplementation "org.apache.groovy:groovy-json"
     testImplementation "org.junit.jupiter:junit-jupiter-api"
@@ -86,4 +87,5 @@ dependencies {
 
     testImplementation "com.squareup.retrofit2:retrofit-mock"
     testImplementation "com.github.tomakehurst:wiremock-jre8"
+    testImplementation "com.github.tomakehurst:wiremock-jre8-standalone"
 }
diff --git a/igor/igor-web/src/main/java/com/netflix/spinnaker/igor/config/HelmConverterFactory.java b/igor/igor-web/src/main/java/com/netflix/spinnaker/igor/config/HelmConverterFactory.java
index f6593a0e57f..7bbad4f3d8c 100644
--- a/igor/igor-web/src/main/java/com/netflix/spinnaker/igor/config/HelmConverterFactory.java
+++ b/igor/igor-web/src/main/java/com/netflix/spinnaker/igor/config/HelmConverterFactory.java
@@ -17,6 +17,7 @@
 package com.netflix.spinnaker.igor.config;
 
 import com.amazonaws.util.IOUtils;
+import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.JavaType;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import java.lang.annotation.Annotation;
@@ -32,7 +33,12 @@
 public class HelmConverterFactory extends Factory {
   private static final MediaType DEFAULT_MEDIA_TYPE =
       MediaType.get("application/json; charset=UTF-8");
-  private final ObjectMapper objectMapper = new ObjectMapper();
+  private final ObjectMapper objectMapper;
+
+  public HelmConverterFactory() {
+    this.objectMapper = new ObjectMapper();
+    this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+  }
 
   @Override
   public Converter<?, RequestBody> requestBodyConverter(
diff --git a/igor/igor-web/src/test/java/com/netflix/spinnaker/igor/helm/accounts/HelmAccountsServiceTest.java b/igor/igor-web/src/test/java/com/netflix/spinnaker/igor/helm/accounts/HelmAccountsServiceTest.java
new file mode 100644
index 00000000000..9a47f5c1b9a
--- /dev/null
+++ b/igor/igor-web/src/test/java/com/netflix/spinnaker/igor/helm/accounts/HelmAccountsServiceTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2025 OpsMx, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.netflix.spinnaker.igor.helm.accounts;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
+import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
+
+import com.github.tomakehurst.wiremock.client.WireMock;
+import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
+import com.netflix.spinnaker.igor.config.HelmConverterFactory;
+import com.netflix.spinnaker.igor.util.RetrofitUtils;
+import com.netflix.spinnaker.kork.retrofit.ErrorHandlingExecutorCallAdapterFactory;
+import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall;
+import java.util.List;
+import okhttp3.OkHttpClient;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import retrofit2.Retrofit;
+
+public class HelmAccountsServiceTest {
+
+  @RegisterExtension
+  static WireMockExtension wmHelmAccounts =
+      WireMockExtension.newInstance().options(wireMockConfig().dynamicPort()).build();
+
+  static HelmAccountsService helmAccountsService;
+
+  @BeforeAll
+  public static void setup() {
+    helmAccountsService =
+        new Retrofit.Builder()
+            .baseUrl(RetrofitUtils.getBaseUrl(wmHelmAccounts.baseUrl()))
+            .client(new OkHttpClient())
+            .addCallAdapterFactory(ErrorHandlingExecutorCallAdapterFactory.getInstance())
+            .addConverterFactory(new HelmConverterFactory())
+            .build()
+            .create(HelmAccountsService.class);
+  }
+
+  @Test
+  public void testGetAllAccounts_with_additionalFields() {
+    // Helm account with an addition field "type"
+    String expectedResponse =
+        "[{\"name\":\"acc1\",\"types\":[\"type1\",\"type2\"],\"type\":\"helm\"}]";
+
+    wmHelmAccounts.stubFor(
+        WireMock.get("/artifacts/credentials").willReturn(aResponse().withBody(expectedResponse)));
+
+    List<ArtifactAccount> accounts =
+        Retrofit2SyncCall.execute(helmAccountsService.getAllAccounts());
+
+    assertThat(accounts).hasSize(1);
+    assertThat(accounts.get(0).name).isEqualTo("acc1");
+    assertThat(accounts.get(0).types).containsExactly("type1", "type2");
+  }
+}


Reviews:
user: login: dbyron-sf
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-17T00:10:47Z
user: login: kirangodishala
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-17T03:54:10Z
user: login: dbyron-sf
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-17T06:01:13Z

Review comments:
diff_hunk: @@ -0,0 +1,56 @@
+package com.netflix.spinnaker.igor.helm.accounts;
path: igor/igor-web/src/test/java/com/netflix/spinnaker/igor/helm/accounts/HelmAccountsServiceTest.java
user: login: dbyron-sf
body: Missing license header
created_at: 2025-05-17T00:10:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,56 @@
+package com.netflix.spinnaker.igor.helm.accounts;
path: igor/igor-web/src/test/java/com/netflix/spinnaker/igor/helm/accounts/HelmAccountsServiceTest.java
user: login: kirangodishala
body: Included now.
created_at: 2025-05-17T03:54:10Z
author_association: CONTRIBUTOR

PR comments:
user: login: dbyron-sf
created_at: 2025-05-17T06:02:11Z
body: @mergifyio backport release-2025.0.x
author_association: CONTRIBUTOR
user: login: mergify[bot]
created_at: 2025-05-17T06:02:39Z
body: > backport release-2025.0.x

#### ✅ Backports have been created

<details>

* [#7079 fix(web): fix HelmConverterFactory for deserialization error (backport #7077)](https://github.com/spinnaker/spinnaker/pull/7079) has been created for branch `release-2025.0.x`

</details>


<!---
DO NOT EDIT
-*- Mergify Payload -*-
{"command": "backport release-2025.0.x", "conclusion": "success", "action_is_running": true}
-*- Mergify Payload End -*-
-->
author_association: CONTRIBUTOR