aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2023-08-24 13:48:47 +0200
committerMartin Polden <mpolden@mpolden.no>2023-08-30 11:17:35 +0200
commit9edc1f9bc437aa2c231c273337dbcf13e78110ac (patch)
tree924c60b0c436d8b446322995f8a94f726df2fee3
parentaca44b3d87564380248f8025c9d4372a5a58a7e7 (diff)
Implement basic services.xml parser
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXml.java92
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXmlTest.java54
2 files changed, 146 insertions, 0 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXml.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXml.java
new file mode 100644
index 00000000000..9eb10857526
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXml.java
@@ -0,0 +1,92 @@
+package com.yahoo.vespa.hosted.controller.application.pkg;
+
+import com.yahoo.text.XML;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * A partially parsed variant of services.xml, for use by the {@link com.yahoo.vespa.hosted.controller.Controller}.
+ *
+ * @author mpolden
+ */
+public record BasicServicesXml(List<Container> containers) {
+
+ public static final BasicServicesXml empty = new BasicServicesXml(List.of());
+
+ private static final String SERVICES_TAG = "services";
+ private static final String CONTAINER_TAG = "container";
+ private static final String CLIENTS_TAG = "clients";
+ private static final String CLIENT_TAG = "client";
+ private static final String TOKEN_TAG = "token";
+
+ public BasicServicesXml(List<Container> containers) {
+ this.containers = List.copyOf(Objects.requireNonNull(containers));
+ }
+
+ /** Parse a services.xml from given document */
+ public static BasicServicesXml parse(Document document) {
+ Element root = document.getDocumentElement();
+ if (!root.getTagName().equals("services")) {
+ throw new IllegalArgumentException("Root tag must be <" + SERVICES_TAG + ">");
+ }
+ List<BasicServicesXml.Container> containers = new ArrayList<>();
+ for (var childNode : XML.getChildren(root)) {
+ if (childNode.getTagName().equals(CONTAINER_TAG)) {
+ String id = childNode.getAttribute("id");
+ if (id.isEmpty()) throw new IllegalArgumentException(CONTAINER_TAG + " tag requires 'id' attribute");
+ List<Container.AuthMethod> methods = parseAuthMethods(childNode);
+ containers.add(new Container(id, methods));
+ }
+ }
+ return new BasicServicesXml(containers);
+ }
+
+ private static List<BasicServicesXml.Container.AuthMethod> parseAuthMethods(Element containerNode) {
+ List<BasicServicesXml.Container.AuthMethod> methods = new ArrayList<>();
+ for (var node : XML.getChildren(containerNode)) {
+ if (node.getTagName().equals(CLIENTS_TAG)) {
+ for (var clientNode : XML.getChildren(node)) {
+ if (clientNode.getTagName().equals(CLIENT_TAG)) {
+ boolean tokenEnabled = XML.getChildren(clientNode).stream()
+ .anyMatch(n -> n.getTagName().equals(TOKEN_TAG));
+ methods.add(tokenEnabled ? Container.AuthMethod.token : Container.AuthMethod.mtls);
+ }
+ }
+ }
+ }
+ if (methods.isEmpty()) {
+ methods.add(Container.AuthMethod.mtls);
+ }
+ return methods;
+ }
+
+ /**
+ * A Vespa container service.
+ *
+ * @param id ID of container
+ * @param authMethods Authentication methods supported by this container
+ */
+ public record Container(String id, List<AuthMethod> authMethods) {
+
+ public Container(String id, List<AuthMethod> authMethods) {
+ this.id = Objects.requireNonNull(id);
+ this.authMethods = Objects.requireNonNull(authMethods).stream()
+ .distinct()
+ .sorted()
+ .collect(Collectors.toList());
+ if (authMethods.isEmpty()) throw new IllegalArgumentException("Container must have at least one auth method");
+ }
+
+ public enum AuthMethod {
+ mtls,
+ token,
+ }
+
+ }
+
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXmlTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXmlTest.java
new file mode 100644
index 00000000000..7d377ef6361
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXmlTest.java
@@ -0,0 +1,54 @@
+package com.yahoo.vespa.hosted.controller.application.pkg;
+
+import com.yahoo.text.XML;
+import com.yahoo.vespa.hosted.controller.application.pkg.BasicServicesXml.Container;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * @author mpolden
+ */
+class BasicServicesXmlTest {
+
+ @Test
+ public void parse() {
+ assertServices(new BasicServicesXml(List.of()), "<services/>");
+ assertServices(new BasicServicesXml(List.of(new Container("foo", List.of(Container.AuthMethod.mtls)),
+ new Container("bar", List.of(Container.AuthMethod.mtls)))),
+ """
+ <services>
+ <container id="foo"/>
+ <container id="bar"/>
+ </services>
+ """);
+ assertServices(new BasicServicesXml(List.of(
+ new Container("foo",
+ List.of(Container.AuthMethod.mtls,
+ Container.AuthMethod.token)),
+ new Container("bar", List.of(Container.AuthMethod.mtls)))),
+ """
+ <services>
+ <container id="foo">
+ <clients>
+ <client id="mtls"/>
+ <client id="token">
+ <token id="my-token"/>
+ </client>
+ <client id="token2">
+ <token id="other-token"/>
+ </client>
+ </clients>
+ </container>
+ <container id="bar"/>
+ </services>
+ """);
+ }
+
+ private void assertServices(BasicServicesXml expected, String xmlForm) {
+ assertEquals(expected, BasicServicesXml.parse(XML.getDocument(xmlForm)));
+ }
+
+}