summaryrefslogtreecommitdiffstats
path: root/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java
diff options
context:
space:
mode:
Diffstat (limited to 'config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java')
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java51
1 files changed, 37 insertions, 14 deletions
diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java b/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java
index 43281c90efe..38562eefb03 100644
--- a/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java
+++ b/config-model-api/src/main/java/com/yahoo/config/application/api/xml/DeploymentSpecXmlReader.java
@@ -25,6 +25,7 @@ import com.yahoo.config.application.api.TimeWindow;
import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.AthenzService;
import com.yahoo.config.provision.CloudAccount;
+import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
@@ -165,7 +166,7 @@ public class DeploymentSpecXmlReader {
optionalIntegerAttribute(majorVersionAttribute, root),
stringAttribute(athenzDomainAttribute, root).map(AthenzDomain::from),
stringAttribute(athenzServiceAttribute, root).map(AthenzService::from),
- stringAttribute(cloudAccountAttribute, root).map(CloudAccount::from),
+ readCloudAccounts(root),
stringAttribute(hostTTLAttribute, root).map(s -> toDuration(s, "empty host TTL")),
applicationEndpoints,
xmlForm,
@@ -205,7 +206,7 @@ public class DeploymentSpecXmlReader {
int maxIdleHours = getWithFallback(instanceElement, parentTag, upgradeTag, "max-idle-hours", Integer::parseInt, 8);
List<DeploymentSpec.ChangeBlocker> changeBlockers = readChangeBlockers(instanceElement, parentTag);
Optional<AthenzService> athenzService = mostSpecificAttribute(instanceElement, athenzServiceAttribute).map(AthenzService::from);
- Optional<CloudAccount> cloudAccount = readCloudAccount(instanceElement);
+ Map<CloudName, CloudAccount> cloudAccounts = readCloudAccounts(instanceElement);
Optional<Duration> hostTTL = mostSpecificAttribute(instanceElement, hostTTLAttribute).map(s -> toDuration(s, "empty host TTL"));
Notifications notifications = readNotifications(instanceElement, parentTag);
@@ -235,7 +236,7 @@ public class DeploymentSpecXmlReader {
changeBlockers,
Optional.ofNullable(prodAttributes.get(globalServiceIdAttribute)),
athenzService,
- cloudAccount,
+ cloudAccounts,
hostTTL,
notifications,
endpoints,
@@ -277,10 +278,10 @@ public class DeploymentSpecXmlReader {
case testTag:
if (Stream.iterate(stepTag, Objects::nonNull, Node::getParentNode)
.anyMatch(node -> prodTag.equals(node.getNodeName()))) {
- return List.of(new DeclaredTest(RegionName.from(XML.getValue(stepTag).trim()), readCloudAccount(stepTag), readHostTTL(stepTag))); // A production test
+ return List.of(new DeclaredTest(RegionName.from(XML.getValue(stepTag).trim()), readHostTTL(stepTag))); // A production test
}
case devTag, perfTag, stagingTag: // Intentional fallthrough from test tag.
- return List.of(new DeclaredZone(Environment.from(stepTag.getTagName()), Optional.empty(), false, athenzService, testerFlavor, readCloudAccount(stepTag), readHostTTL(stepTag)));
+ return List.of(new DeclaredZone(Environment.from(stepTag.getTagName()), Optional.empty(), false, athenzService, testerFlavor, readCloudAccounts(stepTag), readHostTTL(stepTag)));
case prodTag: // regions, delay and parallel may be nested within, but we can flatten them
return XML.getChildren(stepTag).stream()
.flatMap(child -> readNonInstanceSteps(child, prodAttributes, stepTag, defaultBcp).stream())
@@ -670,8 +671,13 @@ public class DeploymentSpecXmlReader {
/** Returns the given non-blank attribute of tag as a string, if any */
private static Optional<String> stringAttribute(String attributeName, Element tag) {
+ return stringAttribute(attributeName, tag, true);
+ }
+
+ /** Returns the given non-blank attribute of tag as a string, if any */
+ private static Optional<String> stringAttribute(String attributeName, Element tag, boolean ignoreBlanks) {
String value = tag.getAttribute(attributeName);
- return Optional.of(value).filter(s -> !s.isBlank());
+ return Optional.of(value).filter(s -> (tag.getAttributeNode(attributeName) != null && ! ignoreBlanks || ! s.isBlank()));
}
/** Returns the given non-blank attribute of tag or throw */
@@ -685,11 +691,23 @@ public class DeploymentSpecXmlReader {
Optional<String> testerFlavor, Element regionTag) {
return new DeclaredZone(environment, Optional.of(RegionName.from(XML.getValue(regionTag).trim())),
readActive(regionTag), athenzService, testerFlavor,
- readCloudAccount(regionTag), readHostTTL(regionTag));
- }
-
- private Optional<CloudAccount> readCloudAccount(Element tag) {
- return mostSpecificAttribute(tag, cloudAccountAttribute).map(CloudAccount::from);
+ readCloudAccounts(regionTag), readHostTTL(regionTag));
+ }
+
+ private Map<CloudName, CloudAccount> readCloudAccounts(Element tag) {
+ return mostSpecificAttribute(tag, cloudAccountAttribute, false)
+ .map(value -> {
+ Map<CloudName, CloudAccount> accounts = new HashMap<>();
+ for (String part : value.split(",")) {
+ CloudAccount account = CloudAccount.from(part);
+ accounts.merge(account.cloudName(), account, (o, n) -> {
+ throw illegal("both '" + o.account() + "' and '" + n.account() + "' " +
+ "are declared for cloud '" + o.cloudName() + "', in '" + value + "'");
+ });
+ }
+ return accounts;
+ })
+ .orElse(Map.of());
}
private Optional<Duration> readHostTTL(Element tag) {
@@ -802,14 +820,19 @@ public class DeploymentSpecXmlReader {
}
/** Returns the given attribute from the given tag or its closest ancestor with the attribute. */
- private static Optional<String> mostSpecificAttribute(Element tag, String attributeName) {
+ private static Optional<String> mostSpecificAttribute(Element tag, String attributeName, boolean ignoreBlanks) {
return Stream.iterate(tag, Objects::nonNull, Node::getParentNode)
.filter(Element.class::isInstance)
.map(Element.class::cast)
- .flatMap(element -> stringAttribute(attributeName, element).stream())
+ .flatMap(element -> stringAttribute(attributeName, element, ignoreBlanks).stream())
.findFirst();
}
+ /** Returns the given attribute from the given tag or its closest ancestor with the attribute. */
+ private static Optional<String> mostSpecificAttribute(Element tag, String attributeName) {
+ return mostSpecificAttribute(tag, attributeName, true);
+ }
+
/**
* Returns a string consisting of a number followed by "m", "h" or "d" to a duration given in that unit,
* or zero duration if null or blank.
@@ -851,7 +874,7 @@ public class DeploymentSpecXmlReader {
}
}
- private static void illegal(String message) {
+ private static IllegalArgumentException illegal(String message) {
throw new IllegalArgumentException(message);
}