diff --git a/bin/bconst b/bin/bconst new file mode 100755 index 00000000..7a925a32 --- /dev/null +++ b/bin/bconst @@ -0,0 +1,35 @@ +#!/bin/bash +# +# Write a constant value to stdout. +# +# This will only ever write the constant value to stdout if it can successfully be read. +# If the constant can be read, its value is printed with the .toString() method. +# If an error occurs, nothing is written to stdout, and the error will be written to stderr. +# If the value of the constant is null, nothing is printed to stdout, and "null" is printed to stderr. +# +# Usage: +# +# bconst classAndMember +# +# classAndMember : the full name of a Java class, followed by a dot and the constant member name +# +# For example: +# +# bconst bubble.ApiConstants.ROOT_NETWORK_UUID +# +# Environment variables +# +# BUBBLE_API : which API to use. Default is local (http://127.0.0.1:PORT, where PORT is found in .bubble.env) +# BUBBLE_USER : account to use. Default is root +# BUBBLE_PASS : password for account. Default is root +# BUBBLE_INCLUDE : path to look for JSON include files. default value is to assume we are being run from +# bubble repo, bubble-models repo, or bubble-client and use include files from minimal model. +# +SCRIPT="${0}" +SCRIPT_DIR=$(cd $(dirname ${SCRIPT}) && pwd) +. ${SCRIPT_DIR}/bubble_common + +CLASS_AND_MEMBER="${1:?sole param should be something like: bubble.pkg.SomeClass.SOME_CONSTANT}" +shift + +BUBBLE_QUIET=1 exec ${SCRIPT_DIR}/bubble const "${CLASS_AND_MEMBER}" "${@}" diff --git a/bin/prep_bubble_jar b/bin/prep_bubble_jar index aef5e840..53e50374 100755 --- a/bin/prep_bubble_jar +++ b/bin/prep_bubble_jar @@ -57,10 +57,9 @@ if [[ ! -d ${ROLES_DIR} ]] ; then die "automation/roles dir not found: ${ROLES_DIR}" fi -API_CONSTANTS="${BUBBLE_SERVER}/src/main/java/bubble/ApiConstants.java" -LOCAL_NET_ID="$(cat "${API_CONSTANTS}" | grep -v '//' | grep -m 1 "String ROOT_NETWORK_UUID" | awk -F '"' '{print $2}')" +LOCAL_NET_ID="$("${SCRIPT_DIR}/bconst" bubble.ApiConstants.ROOT_NETWORK_UUID 2> /dev/null)" if [[ -z "${LOCAL_NET_ID}" ]] ; then - die "ROOT_NETWORK_UUID could not be read from ${API_CONSTANTS}" + die "ROOT_NETWORK_UUID could not be read from ApiConstants" fi echo "lbs = ${LOCALSTORAGE_BASE_DIR}" diff --git a/bubble-server/src/main/java/bubble/cloud/NoopCloud.java b/bubble-server/src/main/java/bubble/cloud/NoopCloud.java new file mode 100644 index 00000000..49341d04 --- /dev/null +++ b/bubble-server/src/main/java/bubble/cloud/NoopCloud.java @@ -0,0 +1,244 @@ +package bubble.cloud; + +import bubble.cloud.auth.AuthenticationDriver; +import bubble.cloud.compute.ComputeNodeSize; +import bubble.cloud.compute.ComputeNodeSizeType; +import bubble.cloud.compute.ComputeServiceDriver; +import bubble.cloud.dns.DnsServiceDriver; +import bubble.cloud.email.EmailServiceDriver; +import bubble.cloud.geoCode.GeoCodeResult; +import bubble.cloud.geoCode.GeoCodeServiceDriver; +import bubble.cloud.geoLocation.GeoLocateServiceDriver; +import bubble.cloud.geoLocation.GeoLocation; +import bubble.cloud.geoTime.GeoTimeServiceDriver; +import bubble.cloud.geoTime.GeoTimeZone; +import bubble.cloud.payment.PaymentServiceDriver; +import bubble.cloud.sms.SmsServiceDriver; +import bubble.cloud.storage.StorageServiceDriver; +import bubble.model.account.Account; +import bubble.model.account.AccountContact; +import bubble.model.account.message.AccountMessage; +import bubble.model.bill.AccountPaymentMethod; +import bubble.model.bill.BubblePlan; +import bubble.model.bill.PaymentMethodType; +import bubble.model.cloud.*; +import bubble.notify.payment.PaymentValidationResult; +import bubble.notify.storage.StorageListing; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.extern.slf4j.Slf4j; +import org.cobbzilla.util.dns.DnsRecord; +import org.cobbzilla.util.dns.DnsRecordMatch; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.List; + +@Slf4j +public class NoopCloud implements + AuthenticationDriver, ComputeServiceDriver, DnsServiceDriver, EmailServiceDriver, + GeoCodeServiceDriver, GeoLocateServiceDriver, GeoTimeServiceDriver, PaymentServiceDriver, + SmsServiceDriver, StorageServiceDriver { + + public static final String NOOP_CLOUD = NoopCloud.class.getName(); + + @Override public boolean send(Account account, AccountMessage message, AccountContact contact) { + if (log.isDebugEnabled()) log.debug("send( account="+ account + ")"); + return false; + } + + @Override public boolean test() { + if (log.isDebugEnabled()) log.debug("test()"); + return false; + } + + @Override public boolean _write(String fromNode, String key, InputStream data, StorageMetadata metadata, String requestId) throws IOException { + if (log.isDebugEnabled()) log.debug("_write( fromNode="+ fromNode + ")"); + return false; + } + + @Override public boolean canWrite(String fromNode, String toNode, String key) { + if (log.isDebugEnabled()) log.debug("canWrite( fromNode="+ fromNode + ")"); + return false; + } + + @Override public boolean delete(String fromNode, String uri) { + if (log.isDebugEnabled()) log.debug("delete( fromNode="+ fromNode + ")"); + return false; + } + + @Override public boolean deleteNetwork(String networkUuid) throws IOException { + if (log.isDebugEnabled()) log.debug("deleteNetwork( networkUuid="+ networkUuid + ")"); + return false; + } + + @Override public boolean rekey(String fromNode, CloudService newCloud) throws IOException { + if (log.isDebugEnabled()) log.debug("rekey( fromNode="+ fromNode + ")"); + return false; + } + + @Override public StorageListing list(String fromNode, String prefix) throws IOException { + if (log.isDebugEnabled()) log.debug("list( fromNode="+ fromNode + ")"); + return null; + } + + @Override public StorageListing listNext(String fromNode, String listingId) throws IOException { + if (log.isDebugEnabled()) log.debug("listNext( fromNode="+ fromNode + ")"); + return null; + } + + @Override public void setConfig(JsonNode json, CloudService cloudService) { + if (log.isDebugEnabled()) log.debug("setConfig( json="+ json + ")"); + + } + + @Override public CloudCredentials getCredentials() { + if (log.isDebugEnabled()) log.debug("getCredentials()"); + return null; + } + + @Override public void setCredentials(CloudCredentials creds) { + if (log.isDebugEnabled()) log.debug("setCredentials( creds="+ creds + ")"); + + } + + @Override public CloudServiceType getType() { + if (log.isDebugEnabled()) log.debug("getType()"); + return null; + } + + @Override public boolean _exists(String fromNode, String key) throws IOException { + if (log.isDebugEnabled()) log.debug("_exists( fromNode="+ fromNode + ")"); + return false; + } + + @Override public StorageMetadata readMetadata(String fromNode, String key) { + if (log.isDebugEnabled()) log.debug("readMetadata( fromNode="+ fromNode + ")"); + return null; + } + + @Override public InputStream _read(String fromNode, String key) throws IOException { + if (log.isDebugEnabled()) log.debug("_read( fromNode="+ fromNode + ")"); + return null; + } + + @Override public PaymentMethodType getPaymentMethodType() { + if (log.isDebugEnabled()) log.debug("getPaymentMethodType()"); + return null; + } + + @Override public PaymentValidationResult validate(AccountPaymentMethod paymentMethod) { + if (log.isDebugEnabled()) log.debug("validate( paymentMethod="+ paymentMethod + ")"); + return null; + } + + @Override public boolean authorize(BubblePlan plan, String accountPlanUuid, String billUuid, AccountPaymentMethod paymentMethod) { + if (log.isDebugEnabled()) log.debug("authorize( plan="+ plan + ")"); + return false; + } + + @Override public boolean cancelAuthorization(BubblePlan plan, String accountPlanUuid, AccountPaymentMethod paymentMethod) { + if (log.isDebugEnabled()) log.debug("cancelAuthorization( plan="+ plan + ")"); + return false; + } + + @Override public boolean purchase(String accountPlanUuid, String paymentMethodUuid, String billUuid) { + if (log.isDebugEnabled()) log.debug("purchase( accountPlanUuid="+ accountPlanUuid + ")"); + return false; + } + + @Override public boolean refund(String accountPlanUuid) { + if (log.isDebugEnabled()) log.debug("refund( accountPlanUuid="+ accountPlanUuid + ")"); + return false; + } + + @Override public GeoTimeZone getTimezone(String lat, String lon) { + if (log.isDebugEnabled()) log.debug("getTimezone( lat="+ lat + ")"); + return null; + } + + @Override public GeoLocation geolocate(String ip) { + if (log.isDebugEnabled()) log.debug("geolocate( ip="+ ip + ")"); + return null; + } + + @Override public GeoCodeResult lookup(GeoLocation location) { + if (log.isDebugEnabled()) log.debug("lookup( location="+ location + ")"); + return null; + } + + @Override public Collection create(BubbleDomain domain) { + if (log.isDebugEnabled()) log.debug("create( domain="+ domain + ")"); + return null; + } + + @Override public Collection setNetwork(BubbleNetwork network) { + if (log.isDebugEnabled()) log.debug("setNetwork( network="+ network + ")"); + return null; + } + + @Override public Collection setNode(BubbleNode node) { + if (log.isDebugEnabled()) log.debug("setNode( node="+ node + ")"); + return null; + } + + @Override public Collection deleteNode(BubbleNode node) { + if (log.isDebugEnabled()) log.debug("deleteNode( node="+ node + ")"); + return null; + } + + @Override public DnsRecord update(DnsRecord record) { + if (log.isDebugEnabled()) log.debug("update( record="+ record + ")"); + return null; + } + + @Override public DnsRecord remove(DnsRecord record) { + if (log.isDebugEnabled()) log.debug("remove( record="+ record + ")"); + return null; + } + + @Override public Collection list(DnsRecordMatch matcher) { + if (log.isDebugEnabled()) log.debug("list( matcher="+ matcher + ")"); + return null; + } + + @Override public List getSizes() { + if (log.isDebugEnabled()) log.debug("getSizes()"); + return null; + } + + @Override public ComputeNodeSize getSize(ComputeNodeSizeType type) { + if (log.isDebugEnabled()) log.debug("getSize( type="+ type + ")"); + return null; + } + + @Override public BubbleNode start(BubbleNode node) throws Exception { + if (log.isDebugEnabled()) log.debug("start( node="+ node + ")"); + return null; + } + + @Override public BubbleNode cleanupStart(BubbleNode node) throws Exception { + if (log.isDebugEnabled()) log.debug("cleanupStart( node="+ node + ")"); + return null; + } + + @Override public BubbleNode stop(BubbleNode node) throws Exception { + if (log.isDebugEnabled()) log.debug("stop( node="+ node + ")"); + return null; + } + + @Override public BubbleNode status(BubbleNode node) throws Exception { + if (log.isDebugEnabled()) log.debug("status( node="+ node + ")"); + return null; + } + + @Override public List getRegions() { + if (log.isDebugEnabled()) log.debug("getRegions()"); + return null; + } + + @Override public CloudRegion getRegion(String region) { + if (log.isDebugEnabled()) log.debug("getRegion( region="+ region + ")"); + return null; + } +} diff --git a/bubble-server/src/main/java/bubble/dao/account/AccountDAO.java b/bubble-server/src/main/java/bubble/dao/account/AccountDAO.java index 6a2c7b79..387a4578 100644 --- a/bubble-server/src/main/java/bubble/dao/account/AccountDAO.java +++ b/bubble-server/src/main/java/bubble/dao/account/AccountDAO.java @@ -197,6 +197,7 @@ public class AccountDAO extends AbstractCRUDDAO implements SqlViewSearc }); ready.set(true); + cloudDAO.ensureNoopCloudsExist(account); copyTemplateObjects(acct, parent, roleDAO); final Map drivers = new HashMap<>(); diff --git a/bubble-server/src/main/java/bubble/dao/cloud/CloudServiceDAO.java b/bubble-server/src/main/java/bubble/dao/cloud/CloudServiceDAO.java index c7cc31a3..7b961fa7 100644 --- a/bubble-server/src/main/java/bubble/dao/cloud/CloudServiceDAO.java +++ b/bubble-server/src/main/java/bubble/dao/cloud/CloudServiceDAO.java @@ -8,6 +8,7 @@ import bubble.model.account.Account; import bubble.model.cloud.BubbleNetwork; import bubble.model.cloud.CloudService; import bubble.server.BubbleConfiguration; +import lombok.extern.slf4j.Slf4j; import org.cobbzilla.wizard.validation.ValidationResult; import org.hibernate.criterion.Order; import org.springframework.beans.factory.annotation.Autowired; @@ -16,17 +17,49 @@ import org.springframework.stereotype.Repository; import java.util.List; import static bubble.ApiConstants.ROOT_NETWORK_UUID; +import static bubble.cloud.NoopCloud.NOOP_CLOUD; import static bubble.cloud.storage.local.LocalStorageDriver.LOCAL_STORAGE; import static bubble.model.cloud.CloudService.testDriver; +import static org.cobbzilla.util.daemon.ZillaRuntime.*; import static org.cobbzilla.wizard.model.Identifiable.UUID; import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; +import static org.cobbzilla.wizard.server.RestServerBase.reportError; -@Repository +@Repository @Slf4j public class CloudServiceDAO extends AccountOwnedTemplateDAO { @Autowired private AccountDAO accountDAO; @Autowired private BubbleConfiguration configuration; + public void ensureNoopCloudsExist(Account account) { + for (CloudServiceType type : CloudServiceType.values()) { + final CloudService noopCloud = findByAccountNoopForType(account.getUuid(), type); + if (empty(noopCloud)) { + try { + create(new CloudService() + .setAccount(account.getUuid()) + .setType(type) + .setName(NOOP_CLOUD) + .setDescription(NOOP_CLOUD) + .setPriority(Integer.MIN_VALUE) + .setEnabled(true) + .setTemplate(false) + .setSkipTest(true) + .setCredentialsJson("{}") + .setDriverConfigJson("{}") + .setDriverClass(NOOP_CLOUD)); + } catch (Exception e) { + reportError("ensureNoopCloudExists: " + shortError(e)); + } + } + } + } + + private CloudService findByAccountNoopForType(String accountUuid, CloudServiceType type) { + final List found = findByAccountAndTypeAndDriverClass(accountUuid, type, NOOP_CLOUD); + return found.isEmpty() ? null : found.size() == 1 ? found.get(0) : die("findByAccountNoopForType("+accountUuid+", "+type+"): "+found.size()+" results found, expected only one"); + } + @Override public Order getDefaultSortOrder() { return PRIORITY_ASC; } @Override public Object preCreate(CloudService cloud) { diff --git a/bubble-server/src/main/java/bubble/main/BubbleMain.java b/bubble-server/src/main/java/bubble/main/BubbleMain.java index a73e4220..e6f34c5d 100644 --- a/bubble-server/src/main/java/bubble/main/BubbleMain.java +++ b/bubble-server/src/main/java/bubble/main/BubbleMain.java @@ -6,6 +6,7 @@ import bubble.main.http.BubbleHttpPostMain; import bubble.main.http.BubbleHttpPutMain; import bubble.server.BubbleServer; import org.cobbzilla.util.collection.MapBuilder; +import org.cobbzilla.util.main.ConstMain; import org.cobbzilla.util.string.StringUtil; import org.cobbzilla.wizard.main.MainBase; import org.slf4j.bridge.SLF4JBridgeHandler; @@ -28,7 +29,8 @@ public class BubbleMain { {"handlebars", BubbleHandlebarsMain.class}, {"crypt", CryptMain.class}, {"rekey", RekeyDatabaseMain.class}, - {"generate-algo-conf", GenerateAlgoConfMain.class} + {"generate-algo-conf", GenerateAlgoConfMain.class}, + {"const", ConstMain.class} }); public static void main(String[] args) throws Exception { diff --git a/bubble-server/src/main/java/bubble/model/cloud/CloudService.java b/bubble-server/src/main/java/bubble/model/cloud/CloudService.java index 82da5174..2506733b 100644 --- a/bubble-server/src/main/java/bubble/model/cloud/CloudService.java +++ b/bubble-server/src/main/java/bubble/model/cloud/CloudService.java @@ -61,6 +61,7 @@ import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD; }) @ECIndexes({ @ECIndex(unique=true, of={"account", "name"}), + @ECIndex(unique=true, of={"account", "type", "driverClass"}, where="driver_class='bubble.cloud.NoopCloud'"), @ECIndex(of={"account", "template", "enabled"}), @ECIndex(of={"template", "enabled"}) }) diff --git a/bubble-server/src/main/java/bubble/service/dbfilter/EntityIterator.java b/bubble-server/src/main/java/bubble/service/dbfilter/EntityIterator.java index 29e93070..022f45e6 100644 --- a/bubble-server/src/main/java/bubble/service/dbfilter/EntityIterator.java +++ b/bubble-server/src/main/java/bubble/service/dbfilter/EntityIterator.java @@ -1,10 +1,12 @@ package bubble.service.dbfilter; +import bubble.cloud.CloudServiceType; import bubble.cloud.storage.local.LocalStorageConfig; import bubble.cloud.storage.local.LocalStorageDriver; import bubble.model.account.AccountSshKey; import bubble.model.account.AccountTemplate; import bubble.model.app.BubbleApp; +import bubble.model.bill.AccountPaymentMethod; import bubble.model.bill.BubblePlanApp; import bubble.model.cloud.BubbleNetwork; import bubble.model.cloud.BubbleNode; @@ -13,13 +15,12 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.cobbzilla.wizard.model.Identifiable; -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; +import java.util.*; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicReference; +import static bubble.cloud.NoopCloud.NOOP_CLOUD; import static bubble.cloud.storage.local.LocalStorageDriver.LOCAL_STORAGE_STANDARD_BASE_DIR; import static bubble.service.dbfilter.EndOfEntityStream.END_OF_ENTITY_STREAM; import static org.cobbzilla.util.daemon.ZillaRuntime.*; @@ -36,6 +37,7 @@ public abstract class EntityIterator implements Iterator { @Getter private final Thread thread; @Getter private final AtomicReference error; private List userApps; + private Map noopClouds = new HashMap<>(); public EntityIterator(AtomicReference error) { this.error = error; @@ -86,11 +88,41 @@ public abstract class EntityIterator implements Iterator { if (CloudService.class.isAssignableFrom(c)) { entities.stream() .filter(cloud -> fullCopy || notPaymentCloud((CloudService) cloud)) - .forEach(e -> add(setLocalStoragePath((CloudService) e))); + .forEach(e -> { + final CloudService cs = (CloudService) e; + if (!fullCopy) { + if (cs.getDriverClass().equals(NOOP_CLOUD)) { + final CloudServiceType type = cs.getType(); + if (noopClouds.containsKey(type)) { + log.warn("addEntities: multiple " + NOOP_CLOUD + " drivers found for type=" + type); + } else { + noopClouds.put(type, cs); + } + } + } + add(setLocalStoragePath(cs)); + }); } else if (AccountSshKey.class.isAssignableFrom(c)) { entities.forEach(e -> add(setInstallKey((AccountSshKey) e, network))); + } else if (!fullCopy && AccountPaymentMethod.class.isAssignableFrom(c)) { + // clear out payment information, set driver to noop + final CloudService noopCloud = noopClouds.get(CloudServiceType.payment); + if (noopCloud == null) { + die("addEntities: "+NOOP_CLOUD+" for payment cloud type not found"); + } else { + entities.forEach(e -> { + final AccountPaymentMethod apm = (AccountPaymentMethod) e; + apm.setMaskedPaymentInfo("") + .setPaymentInfo("") + .setCloud(noopCloud.getUuid()) + .setDeleted(now()) + .setPromotion(null); + add(apm); + }); + } + } else if (!fullCopy && planApps != null && BubbleApp.class.isAssignableFrom(c)) { // only copy enabled apps, make them templates entities.stream().filter(app -> planAppEnabled(((BubbleApp) app).getTemplateApp(), planApps)) diff --git a/bubble-server/src/main/java/bubble/service/dbfilter/FilteredEntityIterator.java b/bubble-server/src/main/java/bubble/service/dbfilter/FilteredEntityIterator.java index 9b00d802..ccdad00a 100644 --- a/bubble-server/src/main/java/bubble/service/dbfilter/FilteredEntityIterator.java +++ b/bubble-server/src/main/java/bubble/service/dbfilter/FilteredEntityIterator.java @@ -30,7 +30,7 @@ public class FilteredEntityIterator extends EntityIterator { private static final List> POST_COPY_ENTITIES = Arrays.asList(new Class[] { BubbleNode.class, BubbleNodeKey.class, Device.class, AccountMessage.class, - ReferralCode.class, AccountPaymentMethod.class, AccountPayment.class, Bill.class, Promotion.class, + ReferralCode.class, AccountPayment.class, Bill.class, Promotion.class, }); private static boolean isPostCopyEntity(Class clazz) { diff --git a/utils/cobbzilla-utils b/utils/cobbzilla-utils index 77831c8f..fb7fe350 160000 --- a/utils/cobbzilla-utils +++ b/utils/cobbzilla-utils @@ -1 +1 @@ -Subproject commit 77831c8f23574ebdc8476dd835f8bbfbd8404338 +Subproject commit fb7fe350cd4fe0f0338d37e551a0112cfa2e7835