This is an automated email from the ASF dual-hosted git repository.

DomGarguilo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/accumulo.git


The following commit(s) were added to refs/heads/main by this push:
     new 4fa738bf07 Unified --Json output (#6419)
4fa738bf07 is described below

commit 4fa738bf07c45f1383d56e04c9246b0323c36335
Author: Arbaaz Khan <[email protected]>
AuthorDate: Wed Sep 30 12:33:40 2026 -0400

    Unified --Json output (#6419)
    
    * new envelope format for json output
---
 .../accumulo/core/cli/CommandOutputEnvelope.java   | 130 +++++++++++++++++++++
 .../apache/accumulo/core/cli/CommandReport.java    |  51 ++++++++
 .../org/apache/accumulo/core/cli/ServerOpts.java   |   4 +
 .../accumulo/server/util/ListCompactions.java      |   4 +-
 .../server/util/ServerKeywordExecutable.java       |  12 ++
 .../accumulo/server/util/adminCommand/Fate.java    |   8 +-
 .../server/util/adminCommand/ServiceStatus.java    |  13 +--
 .../server/util/fateCommand/FateSummaryReport.java |   9 +-
 .../util/serviceStatus/ServiceStatusReport.java    |  28 ++++-
 .../accumulo/test/fate/FateOpsCommandsITBase.java  |  40 +++++--
 10 files changed, 268 insertions(+), 31 deletions(-)

diff --git 
a/core/src/main/java/org/apache/accumulo/core/cli/CommandOutputEnvelope.java 
b/core/src/main/java/org/apache/accumulo/core/cli/CommandOutputEnvelope.java
new file mode 100644
index 0000000000..c9e12b6b2d
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/cli/CommandOutputEnvelope.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   https://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 org.apache.accumulo.core.cli;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import com.google.gson.Gson;
+
+/**
+ * A stable, versioned outer wrapper for all admin command JSON output.
+ *
+ * <p>
+ * Every command that supports --json output wraps its command-specific data 
in this envelope. This
+ * provides a consistent structure that scripts can rely on regardless of 
which command produced the
+ * output:
+ *
+ * <pre>
+ * {
+ *   "command": "accumulo admin fate --summary",
+ *   "version": "1",
+ *   "reportTime": "2026-06-04T12:00:00Z",
+ *   "status": "OK"
+ *   },
+ *   "output": { ... command-specific payload... }
+ * }
+ * </pre>
+ *
+ * <p>
+ * The {@link CommandStatus#version} field is a stability contract. When a 
breaking change is made
+ * to the envelope structure, the version will be incremented. Scripts should 
check this field and
+ * handle the version they were written against.
+ *
+ */
+public class CommandOutputEnvelope {
+
+  /**
+   * Current envelop schema version. Increment this if a breaking structural 
change is made to the
+   * envelope fields (not to the {@code output} field, data changes command 
specific).
+   */
+  public static final String VERSION = "1.0";
+  private static final DateTimeFormatter ISO_FMT = 
DateTimeFormatter.ISO_OFFSET_DATE_TIME;
+  private static final Gson PRETTY_GSON =
+      new Gson().newBuilder().setPrettyPrinting().disableJdkUnsafe().create();
+
+  public static class CommandStatus {
+    private String command;
+    private String version;
+    private String reportTime;
+    private String statusMessage;
+
+    @SuppressWarnings("unused")
+    private CommandStatus() {}
+
+    private CommandStatus(String command, String statusMessage) {
+      this.command = command;
+      this.version = VERSION;
+      this.reportTime = 
ISO_FMT.format(ZonedDateTime.now(ZoneId.systemDefault()));
+      this.statusMessage = statusMessage;
+    }
+
+    public String getCommand() {
+      return command;
+    }
+
+    public String getVersion() {
+      return version;
+    }
+
+    public String getReportTime() {
+      return reportTime;
+    }
+
+    public String getStatusMessage() {
+      return statusMessage;
+    }
+  }
+
+  private CommandStatus status;
+  private Object output;
+
+  @SuppressWarnings("unused")
+  private CommandOutputEnvelope() {}
+
+  private CommandOutputEnvelope(String command, String statusMessage, Object 
output) {
+    this.status = new CommandStatus(command, statusMessage);
+    this.output = output;
+  }
+
+  public static CommandOutputEnvelope of(String command, Object data) {
+    return new CommandOutputEnvelope(command, "OK", data);
+  }
+
+  public static CommandOutputEnvelope error(String command, String message) {
+    return new CommandOutputEnvelope(command, "ERROR" + message, null);
+  }
+
+  public String toJson() {
+    return PRETTY_GSON.toJson(this);
+  }
+
+  public static CommandOutputEnvelope fromJson(String json) {
+    return PRETTY_GSON.fromJson(json, CommandOutputEnvelope.class);
+  }
+
+  public CommandStatus getStatus() {
+    return status;
+  }
+
+  public Object getOutput() {
+    return output;
+  }
+}
diff --git a/core/src/main/java/org/apache/accumulo/core/cli/CommandReport.java 
b/core/src/main/java/org/apache/accumulo/core/cli/CommandReport.java
new file mode 100644
index 0000000000..ba33faa578
--- /dev/null
+++ b/core/src/main/java/org/apache/accumulo/core/cli/CommandReport.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   https://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 org.apache.accumulo.core.cli;
+
+import java.util.List;
+
+/**
+ * Implemented by all command report classes that support both human-readable 
and computer readable
+ * outputs.
+ * <p>
+ * Json output is always wrapped in a {@link CommandOutputEnvelope} to provide 
a stable, versioned
+ * outer structure that scripts can depend on, regardless of which command is 
used.
+ *
+ * <p>
+ * Usage pattern is a command's execute() method:
+ *
+ * <pre>
+ * CommandReport report = buildReport(context, options);
+ * if (options.json()) {
+ *   System.out.println(report.toEnvelopedJson("accumulo admin 'my-command'"));
+ * } else {
+ *   report.formatLines().forEach(System.out::println);
+ * }
+ * </pre>
+ */
+public interface CommandReport {
+  List<String> formatLines();
+
+  Object getData();
+
+  default String toEnvelopedJson(String commandName) {
+    return CommandOutputEnvelope.of(commandName, getData()).toJson();
+  }
+
+}
diff --git a/core/src/main/java/org/apache/accumulo/core/cli/ServerOpts.java 
b/core/src/main/java/org/apache/accumulo/core/cli/ServerOpts.java
index 4d8b39e191..d0a4c38714 100644
--- a/core/src/main/java/org/apache/accumulo/core/cli/ServerOpts.java
+++ b/core/src/main/java/org/apache/accumulo/core/cli/ServerOpts.java
@@ -53,6 +53,10 @@ public class ServerOpts extends Help {
           + " Expected format: -o <key>=<value> [-o <key>=<value>]")
   private List<String> overrides = new ArrayList<>();
 
+  @Parameter(names = {"-j", "--json"},
+      description = "Print output in JSON format. Output is wrapped in 
standard envelope with command, version, reportTime, status and data fields.")
+  public boolean json = false;
+
   private SiteConfiguration siteConfig = null;
 
   public synchronized SiteConfiguration getSiteConfiguration() {
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/ListCompactions.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/ListCompactions.java
index cdbe2b00b5..48959c83fb 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/ListCompactions.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/ListCompactions.java
@@ -157,8 +157,6 @@ public class ListCompactions extends 
ServerKeywordExecutable<RunningCommandOpts>
         description = "display details about the running compactions")
     boolean details = false;
 
-    @Parameter(names = {"-j", "--json"}, description = "format the output as 
json")
-    boolean jsonOutput = false;
   }
 
   public ListCompactions() {
@@ -208,7 +206,7 @@ public class ListCompactions extends 
ServerKeywordExecutable<RunningCommandOpts>
   public void execute(JCommander cl, RunningCommandOpts options) throws 
Exception {
     List<RunningCompactionSummary> compactions =
         getRunningCompactions(getServerContext(), options.details);
-    if (options.jsonOutput) {
+    if (options.json) {
       try {
         Gson gson = new GsonBuilder().setPrettyPrinting().create();
         System.out.println(gson.toJson(compactions));
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/ServerKeywordExecutable.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/ServerKeywordExecutable.java
index 04251f250d..38f6b2524f 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/ServerKeywordExecutable.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/ServerKeywordExecutable.java
@@ -19,6 +19,7 @@
 package org.apache.accumulo.server.util;
 
 import org.apache.accumulo.core.cli.BaseKeywordExecutable;
+import org.apache.accumulo.core.cli.CommandOutputEnvelope;
 import org.apache.accumulo.core.cli.ServerOpts;
 import org.apache.accumulo.core.conf.AccumuloConfiguration;
 import org.apache.accumulo.core.conf.Property;
@@ -49,6 +50,11 @@ public abstract class ServerKeywordExecutable<OPTS extends 
ServerOpts>
     return context;
   }
 
+  protected String getInvokeCommand() {
+    String group = commandGroup().key();
+    return "accumulo" + (group.isBlank() ? "" : " " + group) + " " + keyword();
+  }
+
   @Override
   public void doExecute(JCommander cl, OPTS options) throws Exception {
     // Login as the server on secure HDFS
@@ -58,6 +64,12 @@ public abstract class ServerKeywordExecutable<OPTS extends 
ServerOpts>
         SecurityUtil.serverLogin(conf);
       }
       execute(cl, options);
+    } catch (Exception e) {
+      if (options.json) {
+        System.out
+            .println(CommandOutputEnvelope.error(getInvokeCommand(), 
e.getMessage()).toJson());
+      }
+      throw e;
     }
   }
 }
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/Fate.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/Fate.java
index f2c785c3b9..7289e0cd5f 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/Fate.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/Fate.java
@@ -137,10 +137,6 @@ public class Fate extends 
ServerKeywordExecutable<FateOpts> {
         description = "[<FateId>...] Print a summary of FaTE transactions. 
Print only the FateId's specified or print all transactions if empty. Use -s to 
only print those with certain states. Use -t to only print those with certain 
FateInstanceTypes. Use -j to print the transactions in json.")
     boolean summarize;
 
-    @Parameter(names = {"-j", "--json"},
-        description = "Print transactions in json. Only useful for --summary 
command.")
-    boolean printJson;
-
     @Parameter(names = {"-s", "--state"},
         description = "<state>... Print transactions in the state(s) {NEW, 
IN_PROGRESS, FAILED_IN_PROGRESS, FAILED, SUCCESSFUL}")
     List<String> states = new ArrayList<>();
@@ -386,8 +382,8 @@ public class Fate extends ServerKeywordExecutable<FateOpts> 
{
 
     // gather statistics
     transactions.getTransactions().forEach(report::gatherTxnStatus);
-    if (cmd.printJson) {
-      printLines(Collections.singletonList(report.toJson()));
+    if (cmd.json) {
+      
printLines(Collections.singletonList(report.toEnvelopedJson(getInvokeCommand())));
     } else {
       printLines(report.formatLines());
     }
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/ServiceStatus.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/ServiceStatus.java
index 4b6b519d7d..f8001f8632 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/ServiceStatus.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/ServiceStatus.java
@@ -59,9 +59,6 @@ public class ServiceStatus extends 
ServerKeywordExecutable<ServiceStatusCmdOpts>
 
   static class ServiceStatusCmdOpts extends ServerOpts {
 
-    @Parameter(names = "--json", description = "provide output in json format")
-    boolean json = false;
-
     @Parameter(names = "--showHosts",
         description = "provide a summary of service counts with host details")
     boolean showHosts = false;
@@ -106,17 +103,15 @@ public class ServiceStatus extends 
ServerKeywordExecutable<ServiceStatusCmdOpts>
     ServiceStatusReport report = new ServiceStatusReport(services, 
options.showHosts);
 
     if (options.json) {
-      System.out.println(report.toJson());
+      System.out.println(report.toEnvelopedJson(getInvokeCommand()));
     } else {
-      StringBuilder sb = new StringBuilder(8192);
-      report.report(sb);
-      System.out.println(sb);
+      report.formatLines().forEach(System.out::println);
     }
   }
 
   /**
-   * The manager paths in ZooKeeper are: {@code 
/accumulo/[IID]/managers/lock/zlock#[NUM]} with the
-   * lock data providing a service descriptor with host and port.
+   * op The manager paths in ZooKeeper are: {@code 
/accumulo/[IID]/managers/lock/zlock#[NUM]} with
+   * the lock data providing a service descriptor with host and port.
    */
   @VisibleForTesting
   StatusSummary getManagerStatus(ServerContext context) {
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/fateCommand/FateSummaryReport.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/fateCommand/FateSummaryReport.java
index 704536cd95..e20b276fe3 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/fateCommand/FateSummaryReport.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/fateCommand/FateSummaryReport.java
@@ -32,6 +32,7 @@ import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
 
+import org.apache.accumulo.core.cli.CommandReport;
 import org.apache.accumulo.core.fate.AdminUtil;
 import org.apache.accumulo.core.fate.Fate;
 import org.apache.accumulo.core.fate.FateId;
@@ -41,7 +42,7 @@ import org.apache.accumulo.core.fate.ReadOnlyFateStore;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 
-public class FateSummaryReport {
+public class FateSummaryReport implements CommandReport {
 
   private Map<String,Integer> statusCounts = new TreeMap<>();
   private Map<String,Integer> cmdCounts = new TreeMap<>();
@@ -154,6 +155,7 @@ public class FateSummaryReport {
    *
    * @return formatted report lines.
    */
+  @Override
   public List<String> formatLines() {
     List<String> lines = new ArrayList<>();
 
@@ -185,4 +187,9 @@ public class FateSummaryReport {
 
     return lines;
   }
+
+  @Override
+  public Object getData() {
+    return this;
+  }
 }
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/serviceStatus/ServiceStatusReport.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/serviceStatus/ServiceStatusReport.java
index 0c628cd604..708d51dc43 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/serviceStatus/ServiceStatusReport.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/serviceStatus/ServiceStatusReport.java
@@ -25,9 +25,13 @@ import java.lang.reflect.Field;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.accumulo.core.cli.CommandOutputEnvelope;
+import org.apache.accumulo.core.cli.CommandReport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -35,11 +39,33 @@ import com.google.gson.ExclusionStrategy;
 import com.google.gson.FieldAttributes;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
 
 /**
  * Wrapper for JSON formatted report.
  */
-public class ServiceStatusReport {
+public class ServiceStatusReport implements CommandReport {
+
+  @Override
+  public List<String> formatLines() {
+    StringBuilder sb = new StringBuilder(8192);
+    report(sb);
+    return Arrays.asList(sb.toString().split("\n"));
+  }
+
+  @Override
+  public Object getData() {
+    return this;
+  }
+
+  @Override
+  public String toEnvelopedJson(String commandName) {
+    Gson data = showHosts ? new GsonBuilder().disableJdkUnsafe().create() : 
new GsonBuilder()
+        .disableJdkUnsafe().setExclusionStrategies(new 
HostExclusionStrategy()).create();
+
+    JsonElement dataElement = data.toJsonTree(this, ServiceStatusReport.class);
+    return CommandOutputEnvelope.of(commandName, dataElement).toJson();
+  }
 
   private static class HostExclusionStrategy implements ExclusionStrategy {
 
diff --git 
a/test/src/main/java/org/apache/accumulo/test/fate/FateOpsCommandsITBase.java 
b/test/src/main/java/org/apache/accumulo/test/fate/FateOpsCommandsITBase.java
index 3d338ae9eb..05fbd7f1b6 100644
--- 
a/test/src/main/java/org/apache/accumulo/test/fate/FateOpsCommandsITBase.java
+++ 
b/test/src/main/java/org/apache/accumulo/test/fate/FateOpsCommandsITBase.java
@@ -47,6 +47,7 @@ import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
+import org.apache.accumulo.core.cli.CommandOutputEnvelope;
 import org.apache.accumulo.core.client.Accumulo;
 import org.apache.accumulo.core.client.AccumuloClient;
 import org.apache.accumulo.core.client.IteratorSetting;
@@ -88,6 +89,9 @@ import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
 public abstract class FateOpsCommandsITBase extends SharedMiniClusterBase
     implements FateTestRunner<LatchTestEnv> {
   private static final Logger log = 
LoggerFactory.getLogger(FateOpsCommandsITBase.class);
@@ -134,7 +138,7 @@ public abstract class FateOpsCommandsITBase extends 
SharedMiniClusterBase
       String result = p.readStdOut();
       result = result.lines().filter(line -> 
!line.matches(".*(INFO|DEBUG|WARN|ERROR).*"))
           .collect(Collectors.joining("\n"));
-      FateSummaryReport report = FateSummaryReport.fromJson(result);
+      FateSummaryReport report = parseFateSummaryFromEnvelope(result);
       assertNotNull(report);
       assertNotEquals(0, report.getReportTime());
       assertTrue(report.getStatusCounts().isEmpty());
@@ -157,7 +161,7 @@ public abstract class FateOpsCommandsITBase extends 
SharedMiniClusterBase
       result = p.readStdOut();
       result = result.lines().filter(line -> 
!line.matches(".*(INFO|DEBUG|WARN|ERROR).*"))
           .collect(Collectors.joining("\n"));
-      report = FateSummaryReport.fromJson(result);
+      report = parseFateSummaryFromEnvelope(result);
       assertNotNull(report);
       assertNotEquals(0, report.getReportTime());
       assertFalse(report.getStatusCounts().isEmpty());
@@ -179,7 +183,7 @@ public abstract class FateOpsCommandsITBase extends 
SharedMiniClusterBase
       result = p.readStdOut();
       result = result.lines().filter(line -> 
!line.matches(".*(INFO|DEBUG|WARN|ERROR).*"))
           .collect(Collectors.joining("\n"));
-      report = FateSummaryReport.fromJson(result);
+      report = parseFateSummaryFromEnvelope(result);
       assertNotNull(report);
       assertNotEquals(0, report.getReportTime());
       assertFalse(report.getStatusCounts().isEmpty());
@@ -198,7 +202,7 @@ public abstract class FateOpsCommandsITBase extends 
SharedMiniClusterBase
       result = p.readStdOut();
       result = result.lines().filter(line -> 
!line.matches(".*(INFO|DEBUG|WARN|ERROR).*"))
           .collect(Collectors.joining("\n"));
-      report = FateSummaryReport.fromJson(result);
+      report = parseFateSummaryFromEnvelope(result);
       assertNotNull(report);
       assertNotEquals(0, report.getReportTime());
       assertFalse(report.getStatusCounts().isEmpty());
@@ -218,7 +222,7 @@ public abstract class FateOpsCommandsITBase extends 
SharedMiniClusterBase
       result = p.readStdOut();
       result = result.lines().filter(line -> 
!line.matches(".*(INFO|DEBUG|WARN|ERROR).*"))
           .collect(Collectors.joining("\n"));
-      report = FateSummaryReport.fromJson(result);
+      report = parseFateSummaryFromEnvelope(result);
       assertNotNull(report);
       assertNotEquals(0, report.getReportTime());
       assertFalse(report.getStatusCounts().isEmpty());
@@ -241,7 +245,7 @@ public abstract class FateOpsCommandsITBase extends 
SharedMiniClusterBase
       result = p.readStdOut();
       result = result.lines().filter(line -> 
!line.matches(".*(INFO|DEBUG|WARN|ERROR).*"))
           .collect(Collectors.joining("\n"));
-      report = FateSummaryReport.fromJson(result);
+      report = parseFateSummaryFromEnvelope(result);
       assertNotNull(report);
       assertNotEquals(0, report.getReportTime());
       assertFalse(report.getStatusCounts().isEmpty());
@@ -259,7 +263,7 @@ public abstract class FateOpsCommandsITBase extends 
SharedMiniClusterBase
       result = p.readStdOut();
       result = result.lines().filter(line -> 
!line.matches(".*(INFO|DEBUG|WARN|ERROR).*"))
           .collect(Collectors.joining("\n"));
-      report = FateSummaryReport.fromJson(result);
+      report = parseFateSummaryFromEnvelope(result);
       assertNotNull(report);
       assertNotEquals(0, report.getReportTime());
       assertFalse(report.getStatusCounts().isEmpty());
@@ -281,7 +285,7 @@ public abstract class FateOpsCommandsITBase extends 
SharedMiniClusterBase
       result = p.readStdOut();
       result = result.lines().filter(line -> 
!line.matches(".*(INFO|DEBUG|WARN|ERROR).*"))
           .collect(Collectors.joining("\n"));
-      report = FateSummaryReport.fromJson(result);
+      report = parseFateSummaryFromEnvelope(result);
       assertNotNull(report);
       assertNotEquals(0, report.getReportTime());
       assertFalse(report.getStatusCounts().isEmpty());
@@ -303,7 +307,7 @@ public abstract class FateOpsCommandsITBase extends 
SharedMiniClusterBase
       result = p.readStdOut();
       result = result.lines().filter(line -> 
!line.matches(".*(INFO|DEBUG|WARN|ERROR).*"))
           .collect(Collectors.joining("\n"));
-      report = FateSummaryReport.fromJson(result);
+      report = parseFateSummaryFromEnvelope(result);
       assertNotNull(report);
       assertNotEquals(0, report.getReportTime());
       assertFalse(report.getStatusCounts().isEmpty());
@@ -516,7 +520,7 @@ public abstract class FateOpsCommandsITBase extends 
SharedMiniClusterBase
         String result = p.readStdOut();
         result = result.lines().filter(line -> 
!line.matches(".*(INFO|DEBUG|WARN|ERROR).*"))
             .collect(Collectors.joining("\n"));
-        FateSummaryReport report = FateSummaryReport.fromJson(result);
+        FateSummaryReport report = parseFateSummaryFromEnvelope(result);
 
         // Validate transaction name and transaction step from summary command
 
@@ -904,7 +908,7 @@ public abstract class FateOpsCommandsITBase extends 
SharedMiniClusterBase
     String result = p.readStdOut();
     result = result.lines().filter(line -> 
!line.matches(".*(INFO|DEBUG|WARN|ERROR).*"))
         .collect(Collectors.joining("\n"));
-    FateSummaryReport report = FateSummaryReport.fromJson(result);
+    FateSummaryReport report = parseFateSummaryFromEnvelope(result);
     assertNotNull(report);
     Map<String,String> fateIdToStatus = new HashMap<>();
     report.getFateDetails().forEach((d) -> {
@@ -990,4 +994,18 @@ public abstract class FateOpsCommandsITBase extends 
SharedMiniClusterBase
         args.toArray(new String[0]));
     assertEquals(0, p.getProcess().waitFor());
   }
+
+  private FateSummaryReport parseFateSummaryFromEnvelope(String json) {
+    CommandOutputEnvelope envelope = CommandOutputEnvelope.fromJson(json);
+    assertNotNull(envelope);
+    assertNotNull(envelope.getStatus());
+    assertEquals(CommandOutputEnvelope.VERSION, 
envelope.getStatus().getVersion());
+    assertEquals("OK", envelope.getStatus().getStatusMessage());
+    assertNotNull(envelope.getStatus().getReportTime());
+    assertNotNull(envelope.getStatus().getCommand());
+    assertTrue(envelope.getStatus().getCommand().contains("fate"));
+    Gson gson = new GsonBuilder().disableJdkUnsafe().create();
+    String dataJson = gson.toJson(envelope.getOutput());
+    return FateSummaryReport.fromJson(dataJson);
+  }
 }

Reply via email to