@@ -84,6 +84,8 @@ echo "------------------------------------------------------------" | |||
cat "${UPDATED}" | |||
echo "------------------------------------------------------------" | |||
cd ${LOCALSTORAGE_BASE_DIR} && jar uvf ${BUBBLE_JAR} automation || die "Error updating ${BUBBLE_JAR} with default role archives" | |||
mkdir -p ${CLASSES_DIR}/scripts | |||
for script in run.sh bubble_common bubble bget bgetn bgeti bpost bposte bput bpute bdelete bscript bmodel bencrypt bdecrypt list_bubble_databases cleanup_bubble_databases ; do | |||
cp ${SCRIPT_DIR}/${script} ${CLASSES_DIR}/scripts || die "Error copying ${SCRIPT_DIR}/${script} -> ${CLASSES_DIR}/scripts" | |||
@@ -43,5 +43,5 @@ else | |||
fi | |||
dropdb bubble ; createdb bubble && cat ${SQL_DIR}/bubble.sql | psql bubble | |||
echo "Successfully initialized DB schema from:" | |||
echo "Successfully initialized DB schema from: " | |||
echo ${SQL_DIR}/bubble.sql |
@@ -33,6 +33,8 @@ public class ApiConstants { | |||
public static final int MAX_SEARCH_PAGE = 50; | |||
public static final String ROOT_NETWORK_UUID = "00000000-0000-0000-0000-000000000000"; | |||
@Getter(lazy=true) private static final String bubbleDefaultDomain = initDefaultDomain(); | |||
private static String initDefaultDomain() { | |||
final File f = new File(HOME_DIR, ".BUBBLE_DEFAULT_DOMAIN"); | |||
@@ -2,6 +2,7 @@ package bubble.cloud.storage.local; | |||
import bubble.cloud.CloudServiceDriverBase; | |||
import bubble.cloud.storage.StorageServiceDriver; | |||
import bubble.dao.account.AccountDAO; | |||
import bubble.dao.cloud.BubbleNodeDAO; | |||
import bubble.model.cloud.BubbleNetwork; | |||
import bubble.model.cloud.BubbleNode; | |||
@@ -12,7 +13,6 @@ import lombok.Cleanup; | |||
import org.apache.commons.io.FileUtils; | |||
import org.apache.commons.io.IOUtils; | |||
import org.cobbzilla.util.io.FileUtil; | |||
import org.cobbzilla.wizard.cache.redis.RedisService; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import java.io.*; | |||
@@ -20,6 +20,7 @@ import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import static bubble.ApiConstants.ROOT_NETWORK_UUID; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.notSupported; | |||
import static org.cobbzilla.util.io.FileUtil.*; | |||
@@ -32,7 +33,7 @@ public class LocalStorageDriver extends CloudServiceDriverBase<LocalStorageConfi | |||
public static final String SUFFIX_META = "._bubble_metadata"; | |||
@Autowired private BubbleNodeDAO nodeDAO; | |||
@Autowired private RedisService redis; | |||
@Autowired private AccountDAO accountDAO; | |||
public static final String LOCAL_STORAGE = "LocalStorage"; | |||
public static final String LOCAL_STORAGE_STANDARD_BASE_DIR = "/home/bubble/.bubble_local_storage"; | |||
@@ -43,9 +44,14 @@ public class LocalStorageDriver extends CloudServiceDriverBase<LocalStorageConfi | |||
@Override public boolean _exists(String fromNode, String key) throws IOException { | |||
final BubbleNode from = getFromNode(fromNode); | |||
final File file = keyFile(from, key); | |||
final boolean exists = file.exists(); | |||
return exists; | |||
if (from != null) { | |||
final File file = keyFile(from, key); | |||
return file.exists(); | |||
} | |||
if (activated()) return false; | |||
// check classpath only if bubble has not been activated | |||
@Cleanup final InputStream in = getClass().getClassLoader().getResourceAsStream(key); | |||
return in != null; | |||
} | |||
protected File metaFile(File f) { return new File(abs(f)+SUFFIX_META); } | |||
@@ -61,9 +67,26 @@ public class LocalStorageDriver extends CloudServiceDriverBase<LocalStorageConfi | |||
@Override public InputStream _read(String fromNode, String key) throws IOException { | |||
final BubbleNode from = getFromNode(fromNode); | |||
final File f = keyFile(from, key); | |||
if (!f.exists()) return null; | |||
return new FileInputStream(f); | |||
if (from != null) { | |||
final File f = keyFile(from, key); | |||
return f.exists() ? new FileInputStream(f) : null; | |||
} | |||
// only try classpath is bubble has not been activated | |||
if (activated()) return null; | |||
@Cleanup InputStream in = getClass().getClassLoader().getResourceAsStream(key); | |||
if (in == null) return null; | |||
// copy file to root network storage, so we can find it after activation | |||
final File file = keyFileForNetwork(ROOT_NETWORK_UUID, config.getBaseDir(), key); | |||
@Cleanup OutputStream out = new FileOutputStream(file); | |||
IOUtils.copyLarge(in, out); | |||
return new FileInputStream(file); | |||
} | |||
public boolean activated() { | |||
return accountDAO.activated() && configuration.getThisNode() != null; | |||
} | |||
@Override public boolean _write(String fromNode, String key, InputStream data, StorageMetadata metadata, String requestId) throws IOException { | |||
@@ -131,7 +154,7 @@ public class LocalStorageDriver extends CloudServiceDriverBase<LocalStorageConfi | |||
protected BubbleNode getFromNode(String fromNode) { | |||
final BubbleNode node = nodeDAO.findByUuid(fromNode); | |||
return node != null ? node : die("fromNode not found: "+fromNode); | |||
return node != null ? node : !activated() ? null : die("fromNode not found: "+fromNode); | |||
} | |||
private File keyFile(BubbleNode from, String key) { return keyFile(from, config.getBaseDir(), key); } | |||
@@ -26,6 +26,7 @@ import java.util.stream.Collectors; | |||
import static bubble.ApiConstants.EP_DOMAINS; | |||
import static bubble.model.cloud.AnsibleRole.sameRoleName; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||
import static org.cobbzilla.util.dns.DnsType.NS; | |||
import static org.cobbzilla.util.dns.DnsType.SOA; | |||
import static org.cobbzilla.util.json.JsonUtil.json; | |||
@@ -101,6 +102,7 @@ public class BubbleDomain extends IdentifiableBase implements AccountTemplate { | |||
@Type(type=ENCRYPTED_STRING) @Column(columnDefinition="varchar("+(10000+ENC_PAD)+") NOT NULL") | |||
@JsonIgnore @Getter @Setter private String rolesJson; | |||
public boolean hasRoles () { return !empty(getRoles()); } | |||
@Transient public String[] getRoles () { return rolesJson == null ? null : json(rolesJson, String[].class); } | |||
public BubbleDomain setRoles (String[] roles) { return setRolesJson(roles == null ? null : json(roles)); } | |||
@@ -50,9 +50,8 @@ public class BubbleFootprint extends IdentifiableBase implements AccountTemplate | |||
public static final BubbleFootprint DEFAULT_FOOTPRINT_OBJECT = new BubbleFootprint() | |||
.setName(DEFAULT_FOOTPRINT) | |||
.setDescription("Exclude countries subject to United States OFAC sanctions") | |||
.setTemplate(true) | |||
.setDisallowedCountries(new String[] {"IR", "KP", "SY", "SD", "CU", "VE"}); | |||
.setDescription("No restrictions, run anywhere") | |||
.setTemplate(true); | |||
@SuppressWarnings("unused") | |||
public BubbleFootprint (BubbleFootprint other) { copy(this, other, CREATE_FIELDS); } | |||
@@ -25,6 +25,7 @@ import java.util.Collection; | |||
import java.util.List; | |||
import static bubble.ApiConstants.EP_NETWORKS; | |||
import static bubble.ApiConstants.ROOT_NETWORK_UUID; | |||
import static bubble.model.cloud.BubbleDomain.DOMAIN_NAME_MAXLEN; | |||
import static bubble.model.cloud.BubbleNetworkState.created; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
@@ -55,6 +56,10 @@ public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBu | |||
@Override public Identifiable update(Identifiable other) { copy(this, other, UPDATE_FIELDS); return this; } | |||
@Override public void beforeCreate() { | |||
if (!hasUuid() && !getUuid().equals(ROOT_NETWORK_UUID)) super.beforeCreate(); | |||
} | |||
@Transient @JsonIgnore public String getNetwork () { return getUuid(); } | |||
@ECSearchable | |||
@@ -28,6 +28,7 @@ import org.glassfish.grizzly.http.server.Request; | |||
import org.glassfish.jersey.server.ContainerRequest; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Service; | |||
import org.springframework.transaction.annotation.Transactional; | |||
import javax.validation.Valid; | |||
import javax.ws.rs.*; | |||
@@ -77,10 +78,12 @@ public class AuthResource { | |||
@GET @Path(EP_ACTIVATE) | |||
public Response isActivated(@Context ContainerRequest ctx) { return ok(accountDAO.activated()); } | |||
@Transactional | |||
@PUT @Path(EP_ACTIVATE) | |||
public Response activate(@Context Request req, | |||
@Context ContainerRequest ctx, | |||
@Valid ActivationRequest request) { | |||
if (request == null) return invalid("err.activationRequest.required"); | |||
final Account found = optionalUserPrincipal(ctx); | |||
if (found != null) { | |||
if (!found.admin()) return forbidden(); | |||
@@ -1,5 +1,6 @@ | |||
package bubble.service.boot; | |||
import bubble.ApiConstants; | |||
import bubble.cloud.CloudServiceType; | |||
import bubble.cloud.compute.ComputeNodeSizeType; | |||
import bubble.cloud.compute.local.LocalComputeDriver; | |||
@@ -11,13 +12,11 @@ import bubble.model.account.ActivationRequest; | |||
import bubble.model.cloud.*; | |||
import bubble.server.BubbleConfiguration; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.cobbzilla.wizard.model.IdentifiableBase; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Service; | |||
import java.io.File; | |||
import java.util.Arrays; | |||
import java.util.concurrent.atomic.AtomicReference; | |||
import static bubble.cloud.storage.StorageServiceDriver.STORAGE_PREFIX; | |||
import static bubble.cloud.storage.local.LocalStorageDriver.LOCAL_STORAGE; | |||
@@ -26,7 +25,6 @@ import static bubble.model.cloud.BubbleFootprint.DEFAULT_FOOTPRINT_OBJECT; | |||
import static bubble.model.cloud.BubbleNetwork.TAG_ALLOW_REGISTRATION; | |||
import static bubble.model.cloud.BubbleNetwork.TAG_PARENT_ACCOUNT; | |||
import static java.util.concurrent.TimeUnit.SECONDS; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.background; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
import static org.cobbzilla.util.io.FileUtil.toStringOrDie; | |||
import static org.cobbzilla.util.io.StreamUtil.stream2string; | |||
@@ -40,7 +38,6 @@ public class ActivationService { | |||
public static final String DEFAULT_ROLES = "ansible/default_roles.json"; | |||
public static final String ROOT_NETWORK_UUID = "00000000-0000-0000-0000-000000000000"; | |||
public static final long ROOT_CREATE_TIMEOUT = SECONDS.toMillis(10); | |||
@Autowired private AnsibleRoleDAO roleDAO; | |||
@@ -82,9 +79,15 @@ public class ActivationService { | |||
.setAccount(account.getUuid())); | |||
} | |||
final AnsibleRole[] roles = request.hasRoles() ? request.getRoles() : json(loadDefaultRoles(), AnsibleRole[].class); | |||
for (AnsibleRole role : roles) { | |||
roleDAO.create(role.setAccount(account.getUuid())); | |||
} | |||
final BubbleDomain domain = domainDAO.create(new BubbleDomain(request.getDomain()) | |||
.setAccount(account.getUuid()) | |||
.setPublicDns(publicDns.getUuid()) | |||
.setRoles(Arrays.stream(roles).map(AnsibleRole::getName).toArray(String[]::new)) | |||
.setTemplate(true)); | |||
BubbleFootprint footprint = footprintDAO.findByAccountAndId(account.getUuid(), DEFAULT_FOOTPRINT); | |||
@@ -133,10 +136,6 @@ public class ActivationService { | |||
selfNodeService.setActivated(node); | |||
final AnsibleRole[] roles = request.hasRoles() ? request.getRoles() : json(loadDefaultRoles(), AnsibleRole[].class); | |||
for (AnsibleRole role : roles) { | |||
roleDAO.create(role.setAccount(account.getUuid())); | |||
} | |||
String[] domainRoles = request.getDomain().getRoles(); | |||
if (domainRoles == null || domainRoles.length == 0) { | |||
domainRoles = Arrays.stream(roles).map(AnsibleRole::getName).toArray(String[]::new); | |||
@@ -161,21 +160,8 @@ public class ActivationService { | |||
} | |||
public BubbleNetwork createRootNetwork(BubbleNetwork network) { | |||
network.setUuid(ROOT_NETWORK_UUID); | |||
final AtomicReference<Object> ref = new AtomicReference<>(); | |||
try { | |||
background(() -> { | |||
IdentifiableBase.getEnforceNullUuidOnCreate().set(false); | |||
ref.set(networkDAO.create(network)); | |||
}, | |||
ref::set).join(ROOT_CREATE_TIMEOUT); | |||
} catch (InterruptedException e) { | |||
return die("createRoot: interrupted: "+e); | |||
} | |||
final Object o = ref.get(); | |||
if (o instanceof BubbleNetwork) return (BubbleNetwork) o; | |||
if (o instanceof Exception) return die((Exception) o); | |||
return die("createRoot: ref was unset"); | |||
network.setUuid(ApiConstants.ROOT_NETWORK_UUID); | |||
return networkDAO.create(network); | |||
} | |||
} |
@@ -40,7 +40,7 @@ public class StandardStorageService implements StorageService { | |||
} | |||
} | |||
private String thisNodeId() { return configuration.getThisNode().getUuid(); } | |||
private String thisNodeId() { return configuration.getThisNode() != null ? configuration.getThisNode().getUuid() : null; } | |||
public boolean exists(String account, String uri) { | |||
final StorageTarget target = new StorageTarget(account, uri); | |||
@@ -8,6 +8,7 @@ message_false=False | |||
message_null=null | |||
message_undefined=undefined | |||
err.activationRequest.required=Activation request object is required | |||
err.name.required=Name is required | |||
err.name.tooShort=Name must be at least 4 characters | |||
err.name.tooLong=Name cannot be longer than 100 characters | |||
@@ -37,6 +38,8 @@ err.accountInit.timeout=Timeout initializing new account | |||
# Activation form | |||
form_title_activation=Activate Bubble | |||
field_label_description=Description | |||
field_label_domain=Domain Name | |||
field_label_domain_description=Enter a domain that is managed by this DNS service. Bubbles will be launched within this domain. | |||
field_label_network_name=Network Name | |||
field_label_dns_service=DNS Service | |||
field_label_storage_service=Storage Service | |||
@@ -167,7 +170,7 @@ driver_credential_AWS_SECRET_KEY_bubble.cloud.storage.s3.S3StorageDriver=AWS Sec | |||
driver_config_region_bubble.cloud.storage.s3.S3StorageDriver=AWS Region | |||
driver_config_description_region_bubble.cloud.storage.s3.S3StorageDriver=The AWS Region to use. Must be a valid name in the AWS <a href="https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-core/src/main/java/com/amazonaws/regions/Regions.java">Regions</a> enum class. | |||
driver_config_bucket_bubble.cloud.storage.s3.S3StorageDriver=S3 Bucket Name | |||
driver_config_description_bucket_bubble.cloud.storage.s3.S3StorageDriver=The name of the S3 bucket to use. The credentials provided in AWS Access Key and AWS Secret key must have permissions to read, write, and list objects in this bucket. | |||
driver_config_description_bucket_bubble.cloud.storage.s3.S3StorageDriver=The name of the S3 bucket to use. The credentials provided in AWS Access Key and AWS Secret key must have permissions to read, write, and list objects in this bucket. If the bucket does not exist, it will be created -- in this case the credentials must also have permissions to create new buckets. | |||
driver_config_prefix_bubble.cloud.storage.s3.S3StorageDriver=S3 Bucket Prefix | |||
driver_config_description_prefix_bubble.cloud.storage.s3.S3StorageDriver=The bucket prefix (subdirectory) to use. All objects stored in S3 will be put under this prefix. This allows you to use the same S3 Bucket for multiple Bubbles, as long as they each use a distinct prefix. | |||
driver_config_listFetchSize_bubble.cloud.storage.s3.S3StorageDriver=S3 List Fetch Size | |||
@@ -1 +1 @@ | |||
Subproject commit 6c141907a646db2d5357bff3607391a23407de8c | |||
Subproject commit 3eab904291f158ab130c549a2bdd15bb77f0ec03 |