From df9cb8c078864303e3d59f2c64b236d0c5f00f8b Mon Sep 17 00:00:00 2001 From: nicholascole Date: Thu, 9 Jul 2026 20:58:30 -0700 Subject: [PATCH] feat(client): add Task.runtimeMetadata for host-resolved worker secrets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The server can resolve a worker's declared TaskDef.runtimeMetadata secret names at poll time and deliver the values on the wire-only Task.runtimeMetadata (conductor-oss PR #1255) — never persisted to task input. Add the field to the client Task model so it is not dropped on deserialization. - Map runtimeMetadata with @JsonInclude(NON_EMPTY) (empty omitted). - Lombok @Data supplies the getter/setter; @Builder support included. - TaskRuntimeMetadataSerDeTest: round-trips the field, omits it when empty. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../conductor/common/metadata/tasks/Task.java | 9 +++ .../tasks/TaskRuntimeMetadataSerDeTest.java | 61 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 conductor-client/src/test/java/com/netflix/conductor/common/metadata/tasks/TaskRuntimeMetadataSerDeTest.java diff --git a/conductor-client/src/main/java/com/netflix/conductor/common/metadata/tasks/Task.java b/conductor-client/src/main/java/com/netflix/conductor/common/metadata/tasks/Task.java index e315fd6c9..d2839148d 100644 --- a/conductor-client/src/main/java/com/netflix/conductor/common/metadata/tasks/Task.java +++ b/conductor-client/src/main/java/com/netflix/conductor/common/metadata/tasks/Task.java @@ -28,6 +28,7 @@ import com.netflix.conductor.common.run.tasks.TypedTask; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; import lombok.*; @Data @@ -151,6 +152,14 @@ public boolean isRetriable() { private WorkflowTask workflowTask; + /** + * Secret values the server resolved for this task at poll time and delivered on the wire only + * (never persisted). Populated when the task's {@code TaskDef.runtimeMetadata} declares secret + * names the host resolves from its secret store (conductor-oss PR #1255). Empty/absent otherwise. + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Map runtimeMetadata; + private String domain; // id 31 is reserved diff --git a/conductor-client/src/test/java/com/netflix/conductor/common/metadata/tasks/TaskRuntimeMetadataSerDeTest.java b/conductor-client/src/test/java/com/netflix/conductor/common/metadata/tasks/TaskRuntimeMetadataSerDeTest.java new file mode 100644 index 000000000..1542e517b --- /dev/null +++ b/conductor-client/src/test/java/com/netflix/conductor/common/metadata/tasks/TaskRuntimeMetadataSerDeTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2025 Conductor Authors. + *

+ * 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.conductor.common.metadata.tasks; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * The server delivers host-resolved secret values on {@code Task.runtimeMetadata} (wire-only, never + * persisted) when a worker's {@code TaskDef.runtimeMetadata} declares secret names (conductor-oss PR + * #1255). This verifies the client model round-trips the field and omits it when empty. + */ +public class TaskRuntimeMetadataSerDeTest { + + private ObjectMapper mapper() { + ObjectMapper m = new ObjectMapper(); + m.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE); + m.setVisibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.NONE); + m.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + return m; + } + + @Test + public void runtimeMetadata_roundTrips() throws Exception { + ObjectMapper mapper = mapper(); + Task task = new Task(); + task.setRuntimeMetadata(Map.of("GITHUB_TOKEN", "ghp_secret", "GH_APP_ID", "42")); + + String json = mapper.writeValueAsString(task); + assertTrue(json.contains("\"runtimeMetadata\""), "field must serialize under the wire key"); + + Task back = mapper.readValue(json, Task.class); + assertEquals("ghp_secret", back.getRuntimeMetadata().get("GITHUB_TOKEN")); + assertEquals("42", back.getRuntimeMetadata().get("GH_APP_ID")); + } + + @Test + public void runtimeMetadata_omittedWhenEmpty() throws Exception { + String json = mapper().writeValueAsString(new Task()); + assertFalse(json.contains("runtimeMetadata"), "empty/absent map must not appear on the wire"); + } +}