diff --git a/bubble-server/src/main/java/bubble/dao/bill/AccountPlanDAO.java b/bubble-server/src/main/java/bubble/dao/bill/AccountPlanDAO.java index 74efdb7a..37204387 100644 --- a/bubble-server/src/main/java/bubble/dao/bill/AccountPlanDAO.java +++ b/bubble-server/src/main/java/bubble/dao/bill/AccountPlanDAO.java @@ -15,12 +15,12 @@ import bubble.model.bill.BubblePlan; import bubble.model.cloud.BubbleNetwork; import bubble.model.cloud.BubbleNetworkState; import bubble.model.cloud.CloudService; +import bubble.model.cloud.HostnameValidationResult; import bubble.notify.payment.PaymentValidationResult; import bubble.server.BubbleConfiguration; import bubble.service.bill.RefundService; import bubble.service.cloud.NetworkService; import lombok.extern.slf4j.Slf4j; -import org.cobbzilla.wizard.validation.ValidationResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; @@ -79,8 +79,9 @@ public class AccountPlanDAO extends AccountOwnedEntityDAO { } @Override public Object preCreate(AccountPlan accountPlan) { - final ValidationResult errors = validateHostname(accountPlan, accountDAO, networkDAO); + final HostnameValidationResult errors = validateHostname(accountPlan, accountDAO, networkDAO); if (errors.isInvalid()) throw invalidEx(errors); + if (errors.hasSuggestedName()) accountPlan.setName(errors.getSuggestedName()); if (configuration.paymentsEnabled()) { if (!accountPlan.hasPaymentMethodObject()) throw invalidEx("err.paymentMethod.required"); diff --git a/bubble-server/src/main/java/bubble/dao/cloud/BubbleNetworkDAO.java b/bubble-server/src/main/java/bubble/dao/cloud/BubbleNetworkDAO.java index b6fac0cf..2c154cbe 100644 --- a/bubble-server/src/main/java/bubble/dao/cloud/BubbleNetworkDAO.java +++ b/bubble-server/src/main/java/bubble/dao/cloud/BubbleNetworkDAO.java @@ -14,7 +14,6 @@ import bubble.service.boot.SelfNodeService; import bubble.service.cloud.NetworkService; import lombok.extern.slf4j.Slf4j; import org.cobbzilla.wizard.model.IdentifiableBase; -import org.cobbzilla.wizard.validation.ValidationResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; @@ -42,8 +41,9 @@ public class BubbleNetworkDAO extends AccountOwnedEntityDAO { @Override public Object preCreate(BubbleNetwork network) { if (!network.hasForkHost()) { - final ValidationResult errors = validateHostname(network, accountDAO, this); + final HostnameValidationResult errors = validateHostname(network, accountDAO, this); if (errors.isInvalid()) throw invalidEx(errors); + if (errors.hasSuggestedName()) network.setName(errors.getSuggestedName()); } final AnsibleInstallType installType = network.hasForkHost() && configuration.isSageLauncher() ? AnsibleInstallType.sage diff --git a/bubble-server/src/main/java/bubble/model/cloud/BubbleNetwork.java b/bubble-server/src/main/java/bubble/model/cloud/BubbleNetwork.java index aaf3546a..8280196b 100644 --- a/bubble-server/src/main/java/bubble/model/cloud/BubbleNetwork.java +++ b/bubble-server/src/main/java/bubble/model/cloud/BubbleNetwork.java @@ -25,7 +25,6 @@ import org.cobbzilla.wizard.model.IdentifiableBase; import org.cobbzilla.wizard.model.entityconfig.EntityFieldType; import org.cobbzilla.wizard.model.entityconfig.annotations.*; import org.cobbzilla.wizard.validation.HasValue; -import org.cobbzilla.wizard.validation.ValidationResult; import org.hibernate.annotations.Type; import javax.persistence.*; @@ -180,16 +179,10 @@ public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBu return die("hostFromFqdn("+fqdn+"): expected suffix ."+getNetworkDomain()); } - public static ValidationResult validateHostname(HasNetwork request, - AccountDAO accountDAO, - BubbleNetworkDAO networkDAO) { - return validateHostname(request, new ValidationResult(), accountDAO, networkDAO); - } - - public static ValidationResult validateHostname(HasNetwork request, - ValidationResult errors, - AccountDAO accountDAO, - BubbleNetworkDAO networkDAO) { + public static HostnameValidationResult validateHostname(HasNetwork request, + AccountDAO accountDAO, + BubbleNetworkDAO networkDAO) { + HostnameValidationResult errors = new HostnameValidationResult(); if (!request.hasName()) { errors.addViolation("err.name.required"); } else { @@ -203,15 +196,20 @@ public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBu } else if (name.length() < NETWORK_NAME_MINLEN) { errors.addViolation("err.name.tooShort"); } else { - final BubbleNetwork network = networkDAO.findByNameAndDomainUuid(name, request.getDomain()); - if (network != null && !network.getUuid().equals(request.getNetwork())) { - errors.addViolation("err.name.alreadyInUse"); - } else { - final Account acct = accountDAO.findByName(name); - if (acct != null && !acct.getUuid().equals(request.getAccount())) { - errors.addViolation("err.name.reservedForAccount"); + for (int i=0; i<100; i++) { + final String tryName = i == 0 ? name : name + i; + final BubbleNetwork network = networkDAO.findByNameAndDomainUuid(tryName, request.getDomain()); + if (network != null && !network.getUuid().equals(request.getNetwork())) { + continue; + } else { + final Account acct = accountDAO.findByName(name); + if (acct != null && !acct.getUuid().equals(request.getAccount())) { + continue; + } } + return tryName.equals(name) ? errors : errors.setSuggestedName(tryName); } + errors.addViolation("err.name.alreadyInUse"); } } return errors; diff --git a/bubble-server/src/main/java/bubble/model/cloud/HostnameValidationResult.java b/bubble-server/src/main/java/bubble/model/cloud/HostnameValidationResult.java new file mode 100644 index 00000000..57a357ee --- /dev/null +++ b/bubble-server/src/main/java/bubble/model/cloud/HostnameValidationResult.java @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2020 Bubble, Inc. All rights reserved. + * For personal (non-commercial) use, see license: https://bubblev.com/bubble-license/ + */ +package bubble.model.cloud; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.cobbzilla.wizard.validation.ValidationResult; + +import static org.cobbzilla.util.daemon.ZillaRuntime.empty; + +@NoArgsConstructor @Accessors(chain=true) +public class HostnameValidationResult extends ValidationResult { + + @Getter @Setter private String suggestedName; + public boolean hasSuggestedName () { return !empty(suggestedName); } + +} diff --git a/bubble-server/src/main/java/bubble/resources/bill/AccountPlansResource.java b/bubble-server/src/main/java/bubble/resources/bill/AccountPlansResource.java index df3e89ff..4e5b0047 100644 --- a/bubble-server/src/main/java/bubble/resources/bill/AccountPlansResource.java +++ b/bubble-server/src/main/java/bubble/resources/bill/AccountPlansResource.java @@ -20,10 +20,7 @@ import bubble.model.bill.AccountPaymentMethod; import bubble.model.bill.AccountPlan; import bubble.model.bill.BubblePlan; import bubble.model.bill.PaymentMethodType; -import bubble.model.cloud.BubbleDomain; -import bubble.model.cloud.BubbleFootprint; -import bubble.model.cloud.BubbleNetwork; -import bubble.model.cloud.CloudService; +import bubble.model.cloud.*; import bubble.resources.account.AccountOwnedResource; import bubble.server.BubbleConfiguration; import bubble.service.account.StandardAuthenticatorService; @@ -146,11 +143,11 @@ public class AccountPlansResource extends AccountOwnedResource storageClouds = cloudDAO.findByAccountAndType(caller.getUuid(), CloudServiceType.storage); diff --git a/bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties b/bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties index bb603210..f47c6f02 100644 --- a/bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties +++ b/bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties @@ -170,7 +170,6 @@ err.name.tooShort=Name must be at least 4 characters err.name.tooLong=Name cannot be longer than 100 characters err.name.planMaxAccountLimit=No more accounts can be created on this plan. Upgrade your plan or delete some accounts. err.name.alreadyInUse=Name is already in use -err.name.reservedForAccount=Name is already in use err.name.reserved=Name is reserved err.name.invalid=Name is invalid err.name.networkNameAlreadyExists=Name is already in use