From 9edc1f9bc437aa2c231c273337dbcf13e78110ac Mon Sep 17 00:00:00 2001 From: Martin Polden Date: Thu, 24 Aug 2023 13:48:47 +0200 Subject: Implement basic services.xml parser --- .../application/pkg/BasicServicesXml.java | 92 ++++++++++++++++++++++ .../application/pkg/BasicServicesXmlTest.java | 54 +++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXml.java create mode 100644 controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXmlTest.java 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 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 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 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 methods = parseAuthMethods(childNode); + containers.add(new Container(id, methods)); + } + } + return new BasicServicesXml(containers); + } + + private static List parseAuthMethods(Element containerNode) { + List 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 authMethods) { + + public Container(String id, List 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()), ""); + assertServices(new BasicServicesXml(List.of(new Container("foo", List.of(Container.AuthMethod.mtls)), + new Container("bar", List.of(Container.AuthMethod.mtls)))), + """ + + + + + """); + assertServices(new BasicServicesXml(List.of( + new Container("foo", + List.of(Container.AuthMethod.mtls, + Container.AuthMethod.token)), + new Container("bar", List.of(Container.AuthMethod.mtls)))), + """ + + + + + + + + + + + + + + + """); + } + + private void assertServices(BasicServicesXml expected, String xmlForm) { + assertEquals(expected, BasicServicesXml.parse(XML.getDocument(xmlForm))); + } + +} -- cgit v1.2.3