summaryrefslogtreecommitdiffstats
path: root/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
diff options
context:
space:
mode:
Diffstat (limited to 'controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java138
1 files changed, 101 insertions, 37 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index c8e16634464..6490ade655a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
@@ -111,9 +111,11 @@ import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.DeletedTenant;
import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
+import com.yahoo.vespa.hosted.controller.tenant.TenantAddress;
+import com.yahoo.vespa.hosted.controller.tenant.TenantBilling;
+import com.yahoo.vespa.hosted.controller.tenant.TenantContact;
+import com.yahoo.vespa.hosted.controller.tenant.TenantContacts;
import com.yahoo.vespa.hosted.controller.tenant.TenantInfo;
-import com.yahoo.vespa.hosted.controller.tenant.TenantInfoAddress;
-import com.yahoo.vespa.hosted.controller.tenant.TenantInfoBillingContact;
import com.yahoo.vespa.hosted.controller.versions.VersionStatus;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
import com.yahoo.vespa.serviceview.bindings.ApplicationView;
@@ -504,37 +506,71 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
infoCursor.setString("name", info.name());
infoCursor.setString("email", info.email());
infoCursor.setString("website", info.website());
- infoCursor.setString("invoiceEmail", info.invoiceEmail());
- infoCursor.setString("contactName", info.contactName());
- infoCursor.setString("contactEmail", info.contactEmail());
+ infoCursor.setString("contactName", info.contact().name());
+ infoCursor.setString("contactEmail", info.contact().email());
toSlime(info.address(), infoCursor);
toSlime(info.billingContact(), infoCursor);
+ toSlime(info.contacts(), infoCursor);
}
return new SlimeJsonResponse(slime);
}
- private void toSlime(TenantInfoAddress address, Cursor parentCursor) {
+ private void toSlime(TenantAddress address, Cursor parentCursor) {
if (address.isEmpty()) return;
Cursor addressCursor = parentCursor.setObject("address");
- addressCursor.setString("addressLines", address.addressLines());
- addressCursor.setString("postalCodeOrZip", address.postalCodeOrZip());
+ addressCursor.setString("addressLines", address.address());
+ addressCursor.setString("postalCodeOrZip", address.code());
addressCursor.setString("city", address.city());
- addressCursor.setString("stateRegionProvince", address.stateRegionProvince());
+ addressCursor.setString("stateRegionProvince", address.region());
addressCursor.setString("country", address.country());
}
- private void toSlime(TenantInfoBillingContact billingContact, Cursor parentCursor) {
+ private void toSlime(TenantBilling billingContact, Cursor parentCursor) {
if (billingContact.isEmpty()) return;
Cursor addressCursor = parentCursor.setObject("billingContact");
- addressCursor.setString("name", billingContact.name());
- addressCursor.setString("email", billingContact.email());
- addressCursor.setString("phone", billingContact.phone());
+ addressCursor.setString("name", billingContact.contact().name());
+ addressCursor.setString("email", billingContact.contact().email());
+ addressCursor.setString("phone", billingContact.contact().phone());
toSlime(billingContact.address(), addressCursor);
}
+ private void toSlime(TenantContacts contacts, Cursor parentCursor) {
+ Cursor contactsCursor = parentCursor.setArray("contacts");
+ contacts.all().forEach(contact -> {
+ Cursor contactCursor = contactsCursor.addObject();
+ Cursor audiencesArray = contactCursor.setArray("audiences");
+ contact.audiences().forEach(audience -> audiencesArray.addString(toAudience(audience)));
+ switch (contact.type()) {
+ case EMAIL:
+ var email = (TenantContacts.EmailContact) contact;
+ contactCursor.setString("email", email.email());
+ return;
+ default:
+ throw new IllegalArgumentException("Serialization for contact type not implemented: " + contact.type());
+ }
+ });
+ }
+
+ private static TenantContacts.Audience fromAudience(String value) {
+ switch (value) {
+ case "tenant": return TenantContacts.Audience.TENANT;
+ case "notifications": return TenantContacts.Audience.NOTIFICATIONS;
+ default: throw new IllegalArgumentException("Unknown contact audience '" + value + "'.");
+ }
+ }
+
+ private static String toAudience(TenantContacts.Audience audience) {
+ switch (audience) {
+ case TENANT: return "tenant";
+ case NOTIFICATIONS: return "notifications";
+ default: throw new IllegalArgumentException("Unexpected contact audience '" + audience + "'.");
+ }
+ }
+
+
private HttpResponse updateTenantInfo(String tenantName, HttpRequest request) {
return controller.tenants().get(TenantName.from(tenantName))
.filter(tenant -> tenant.type() == Tenant.Type.cloud)
@@ -551,24 +587,28 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
// Merge info from request with the existing info
Inspector insp = toSlime(request.getData()).get();
- TenantInfo mergedInfo = TenantInfo.EMPTY
+
+ TenantContact mergedContact = TenantContact.empty()
+ .withName(getString(insp.field("contactName"), oldInfo.contact().name()))
+ .withEmail(getString(insp.field("contactEmail"), oldInfo.contact().email()));
+
+ TenantInfo mergedInfo = TenantInfo.empty()
.withName(getString(insp.field("name"), oldInfo.name()))
- .withEmail(getString(insp.field("email"), oldInfo.email()))
- .withWebsite(getString(insp.field("website"), oldInfo.website()))
- .withInvoiceEmail(getString(insp.field("invoiceEmail"), oldInfo.invoiceEmail()))
- .withContactName(getString(insp.field("contactName"), oldInfo.contactName()))
- .withContactEmail(getString(insp.field("contactEmail"), oldInfo.contactEmail()))
+ .withEmail(getString(insp.field("email"), oldInfo.email()))
+ .withWebsite(getString(insp.field("website"), oldInfo.website()))
+ .withContact(mergedContact)
.withAddress(updateTenantInfoAddress(insp.field("address"), oldInfo.address()))
- .withBillingContact(updateTenantInfoBillingContact(insp.field("billingContact"), oldInfo.billingContact()));
+ .withBilling(updateTenantInfoBillingContact(insp.field("billingContact"), oldInfo.billingContact()))
+ .withContacts(updateTenantInfoContacts(insp.field("contacts"), oldInfo.contacts()));
// Assert that we have a valid tenant info
- if (mergedInfo.contactName().isBlank()) {
+ if (mergedInfo.contact().name().isBlank()) {
throw new IllegalArgumentException("'contactName' cannot be empty");
}
- if (mergedInfo.contactEmail().isBlank()) {
+ if (mergedInfo.contact().email().isBlank()) {
throw new IllegalArgumentException("'contactEmail' cannot be empty");
}
- if (! mergedInfo.contactEmail().contains("@")) {
+ if (! mergedInfo.contact().email().contains("@")) {
// email address validation is notoriously hard - we should probably just try to send a
// verification email to this address. checking for @ is a simple best-effort.
throw new IllegalArgumentException("'contactEmail' needs to be an email address");
@@ -590,20 +630,20 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
return new MessageResponse("Tenant info updated");
}
- private TenantInfoAddress updateTenantInfoAddress(Inspector insp, TenantInfoAddress oldAddress) {
+ private TenantAddress updateTenantInfoAddress(Inspector insp, TenantAddress oldAddress) {
if (!insp.valid()) return oldAddress;
- TenantInfoAddress address = TenantInfoAddress.EMPTY
+ TenantAddress address = TenantAddress.empty()
.withCountry(getString(insp.field("country"), oldAddress.country()))
- .withStateRegionProvince(getString(insp.field("stateRegionProvince"), oldAddress.stateRegionProvince()))
+ .withRegion(getString(insp.field("stateRegionProvince"), oldAddress.region()))
.withCity(getString(insp.field("city"), oldAddress.city()))
- .withPostalCodeOrZip(getString(insp.field("postalCodeOrZip"), oldAddress.postalCodeOrZip()))
- .withAddressLines(getString(insp.field("addressLines"), oldAddress.addressLines()));
+ .withCode(getString(insp.field("postalCodeOrZip"), oldAddress.code()))
+ .withAddress(getString(insp.field("addressLines"), oldAddress.address()));
- List<String> fields = List.of(address.addressLines(),
- address.postalCodeOrZip(),
+ List<String> fields = List.of(address.address(),
+ address.code(),
address.country(),
address.city(),
- address.stateRegionProvince());
+ address.region());
if (fields.stream().allMatch(String::isBlank) || fields.stream().noneMatch(String::isBlank))
return address;
@@ -611,7 +651,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
throw new IllegalArgumentException("All address fields must be set");
}
- private TenantInfoBillingContact updateTenantInfoBillingContact(Inspector insp, TenantInfoBillingContact oldContact) {
+ private TenantContact updateTenantInfoContact(Inspector insp, TenantContact oldContact) {
if (!insp.valid()) return oldContact;
String email = getString(insp.field("email"), oldContact.email());
@@ -622,13 +662,37 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
throw new IllegalArgumentException("'email' needs to be an email address");
}
- return TenantInfoBillingContact.EMPTY
+ return TenantContact.empty()
.withName(getString(insp.field("name"), oldContact.name()))
- .withEmail(email)
- .withPhone(getString(insp.field("phone"), oldContact.phone()))
+ .withEmail(getString(insp.field("email"), oldContact.email()))
+ .withPhone(getString(insp.field("phone"), oldContact.phone()));
+ }
+
+ private TenantBilling updateTenantInfoBillingContact(Inspector insp, TenantBilling oldContact) {
+ if (!insp.valid()) return oldContact;
+
+ return TenantBilling.empty()
+ .withContact(updateTenantInfoContact(insp, oldContact.contact()))
.withAddress(updateTenantInfoAddress(insp.field("address"), oldContact.address()));
}
+ private TenantContacts updateTenantInfoContacts(Inspector insp, TenantContacts oldContacts) {
+ if (!insp.valid()) return oldContacts;
+
+ List<TenantContacts.Contact> contacts = SlimeUtils.entriesStream(insp).map(inspector -> {
+ String email = inspector.field("email").asString().trim();
+ List<TenantContacts.Audience> audiences = SlimeUtils.entriesStream(inspector.field("audiences"))
+ .map(audience -> fromAudience(audience.asString()))
+ .collect(Collectors.toUnmodifiableList());
+ if (!email.contains("@")) {
+ throw new IllegalArgumentException("'email' needs to be an email address");
+ }
+ return new TenantContacts.EmailContact(audiences, email);
+ }).collect(toUnmodifiableList());
+
+ return new TenantContacts(contacts);
+ }
+
private HttpResponse notifications(HttpRequest request, Optional<String> tenant, boolean includeTenantFieldInResponse) {
boolean productionOnly = showOnlyProductionInstances(request);
boolean excludeMessages = "true".equals(request.getProperty("excludeMessages"));
@@ -648,6 +712,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
.forEach(notification -> toSlime(notificationsArray.addObject(), notification, includeTenantFieldInResponse, excludeMessages));
return new SlimeJsonResponse(slime);
}
+
private static <T> boolean propertyEquals(HttpRequest request, String property, Function<String, T> mapper, Optional<T> value) {
return Optional.ofNullable(request.getProperty(property))
.map(propertyValue -> value.isPresent() && mapper.apply(propertyValue).equals(value.get()))
@@ -1852,8 +1917,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
User user = getAttribute(request, User.ATTRIBUTE_NAME, User.class);
TenantInfo info = controller.tenants().require(tenant, CloudTenant.class)
.info()
- .withContactName(user.name())
- .withContactEmail(user.email());
+ .withContact(TenantContact.from(user.name(), user.email()));
// Store changes
controller.tenants().lockOrThrow(tenant, LockedTenant.Cloud.class, lockedTenant -> {
lockedTenant = lockedTenant.withInfo(info);