Add Galaxy repository API and clients

This commit is contained in:
Joseph Doherty
2026-04-29 07:27:00 -04:00
parent 047d875fe6
commit 133c83029b
103 changed files with 22788 additions and 39 deletions
@@ -1,5 +1,7 @@
package com.dohertylan.mxgateway.cli;
import com.dohertylan.mxgateway.client.DeployEventStream;
import com.dohertylan.mxgateway.client.GalaxyRepositoryClient;
import com.dohertylan.mxgateway.client.MxEventStream;
import com.dohertylan.mxgateway.client.MxGatewayClient;
import com.dohertylan.mxgateway.client.MxGatewayClientOptions;
@@ -7,15 +9,21 @@ import com.dohertylan.mxgateway.client.MxGatewayClientVersion;
import com.dohertylan.mxgateway.client.MxGatewaySecrets;
import com.dohertylan.mxgateway.client.MxGatewaySession;
import com.dohertylan.mxgateway.client.MxValues;
import galaxy_repository.v1.GalaxyRepositoryOuterClass.DeployEvent;
import galaxy_repository.v1.GalaxyRepositoryOuterClass.GalaxyAttribute;
import galaxy_repository.v1.GalaxyRepositoryOuterClass.GalaxyObject;
import com.google.protobuf.Message;
import com.google.protobuf.util.JsonFormat;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
import mxaccess_gateway.v1.MxaccessGateway.CloseSessionRequest;
import mxaccess_gateway.v1.MxaccessGateway.MxCommandReply;
@@ -83,9 +91,195 @@ public final class MxGatewayCli implements Callable<Integer> {
commandLine.addSubcommand("write", new WriteCommand(clientFactory));
commandLine.addSubcommand("stream-events", new StreamEventsCommand(clientFactory));
commandLine.addSubcommand("smoke", new SmokeCommand(clientFactory));
commandLine.addSubcommand("galaxy-test", new GalaxyTestConnectionCommand());
commandLine.addSubcommand("galaxy-deploy-time", new GalaxyDeployTimeCommand());
commandLine.addSubcommand("galaxy-discover", new GalaxyDiscoverCommand());
commandLine.addSubcommand("galaxy-watch", new GalaxyWatchCommand());
return commandLine;
}
abstract static class GalaxyCommand implements Callable<Integer> {
@Mixin
CommonOptions common = new CommonOptions();
@Option(names = "--json", description = "Write JSON output.")
boolean json;
GalaxyRepositoryClient connect() {
return GalaxyRepositoryClient.connect(common.resolved().toClientOptions());
}
}
@Command(name = "galaxy-test", description = "Calls GalaxyRepository.TestConnection.")
static final class GalaxyTestConnectionCommand extends GalaxyCommand {
@Override
public Integer call() {
try (GalaxyRepositoryClient client = connect()) {
boolean ok = client.testConnection();
PrintWriter out = common.spec.commandLine().getOut();
if (json) {
Map<String, Object> output = new LinkedHashMap<>();
output.put("command", "galaxy-test");
output.put("options", common.redactedJsonMap());
output.put("ok", ok);
out.println(jsonObject(output));
} else {
out.println(ok);
}
}
return 0;
}
}
@Command(name = "galaxy-deploy-time", description = "Calls GalaxyRepository.GetLastDeployTime.")
static final class GalaxyDeployTimeCommand extends GalaxyCommand {
@Override
public Integer call() {
try (GalaxyRepositoryClient client = connect()) {
Optional<Instant> result = client.getLastDeployTime();
PrintWriter out = common.spec.commandLine().getOut();
if (json) {
Map<String, Object> output = new LinkedHashMap<>();
output.put("command", "galaxy-deploy-time");
output.put("options", common.redactedJsonMap());
output.put("present", result.isPresent());
output.put("timeOfLastDeploy", result.map(Instant::toString).orElse(""));
out.println(jsonObject(output));
} else if (result.isPresent()) {
out.println(result.get());
} else {
out.println("(none)");
}
}
return 0;
}
}
@Command(name = "galaxy-discover", description = "Calls GalaxyRepository.DiscoverHierarchy.")
static final class GalaxyDiscoverCommand extends GalaxyCommand {
@Override
public Integer call() {
try (GalaxyRepositoryClient client = connect()) {
List<GalaxyObject> objects = client.discoverHierarchy();
PrintWriter out = common.spec.commandLine().getOut();
if (json) {
Map<String, Object> output = new LinkedHashMap<>();
output.put("command", "galaxy-discover");
output.put("options", common.redactedJsonMap());
output.put("objects", objects.stream().map(MxGatewayCli::galaxyObjectMap).toList());
out.println(jsonObject(output));
} else {
out.printf("count=%d%n", objects.size());
for (GalaxyObject obj : objects) {
out.printf(" %s [%s] attrs=%d%n",
obj.getTagName(), obj.getBrowseName(), obj.getAttributesCount());
}
}
}
return 0;
}
}
@Command(
name = "galaxy-watch",
description = "Streams GalaxyRepository.WatchDeployEvents until cancelled.")
static final class GalaxyWatchCommand extends GalaxyCommand {
@Option(
names = "--last-seen-deploy-time",
description =
"Optional ISO-8601 instant. When supplied, the bootstrap event is suppressed if the cached"
+ " deploy time matches.")
String lastSeenDeployTime;
@Option(names = "--limit", defaultValue = "0", description = "Maximum events to print before exiting.")
int limit;
@Override
public Integer call() {
Instant after = parseInstant(lastSeenDeployTime);
try (GalaxyRepositoryClient client = connect();
DeployEventStream events = client.watchDeployEvents(after)) {
PrintWriter out = common.spec.commandLine().getOut();
Thread shutdownHook = new Thread(events::close, "galaxy-watch-shutdown");
Runtime.getRuntime().addShutdownHook(shutdownHook);
try {
int count = 0;
while (events.hasNext()) {
DeployEvent event = events.next();
if (json) {
out.println(protoJson(event));
} else {
out.printf(
"seq=%d observed=%s deployTime=%s objects=%d attributes=%d%n",
event.getSequence(),
formatTimestamp(event.getObservedAt()),
event.getTimeOfLastDeployPresent()
? formatTimestamp(event.getTimeOfLastDeploy())
: "(none)",
event.getObjectCount(),
event.getAttributeCount());
}
out.flush();
count++;
if (limit > 0 && count >= limit) {
events.close();
break;
}
}
} finally {
try {
Runtime.getRuntime().removeShutdownHook(shutdownHook);
} catch (IllegalStateException ignored) {
// JVM is already shutting down.
}
}
}
return 0;
}
private static Instant parseInstant(String value) {
if (value == null || value.isBlank()) {
return null;
}
return Instant.parse(value);
}
private static String formatTimestamp(com.google.protobuf.Timestamp ts) {
return Instant.ofEpochSecond(ts.getSeconds(), ts.getNanos()).toString();
}
}
private static Map<String, Object> galaxyObjectMap(GalaxyObject obj) {
Map<String, Object> values = new LinkedHashMap<>();
values.put("gobjectId", obj.getGobjectId());
values.put("tagName", obj.getTagName());
values.put("containedName", obj.getContainedName());
values.put("browseName", obj.getBrowseName());
values.put("parentGobjectId", obj.getParentGobjectId());
values.put("isArea", obj.getIsArea());
values.put("categoryId", obj.getCategoryId());
values.put("hostedByGobjectId", obj.getHostedByGobjectId());
values.put("templateChain", new ArrayList<>(obj.getTemplateChainList()));
List<Map<String, Object>> attrs = new ArrayList<>();
for (GalaxyAttribute attr : obj.getAttributesList()) {
Map<String, Object> attrMap = new LinkedHashMap<>();
attrMap.put("attributeName", attr.getAttributeName());
attrMap.put("fullTagReference", attr.getFullTagReference());
attrMap.put("mxDataType", attr.getMxDataType());
attrMap.put("dataTypeName", attr.getDataTypeName());
attrMap.put("isArray", attr.getIsArray());
attrMap.put("arrayDimension", attr.getArrayDimension());
attrMap.put("arrayDimensionPresent", attr.getArrayDimensionPresent());
attrMap.put("mxAttributeCategory", attr.getMxAttributeCategory());
attrMap.put("securityClassification", attr.getSecurityClassification());
attrMap.put("isHistorized", attr.getIsHistorized());
attrMap.put("isAlarm", attr.getIsAlarm());
attrs.add(attrMap);
}
values.put("attributes", attrs);
return values;
}
@Command(name = "version", description = "Prints the Java client version.")
public static final class VersionCommand implements Callable<Integer> {
@Spec