@@ -197,6 +197,27 @@ | |||
<version>${aws.sdk.version}</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.github.docker-java</groupId> | |||
<artifactId>docker-java-core</artifactId> | |||
<version>3.2.6</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.github.docker-java</groupId> | |||
<artifactId>docker-java-api</artifactId> | |||
<version>3.2.6</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.github.docker-java</groupId> | |||
<artifactId>docker-java-transport-httpclient5</artifactId> | |||
<version>3.2.6</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.github.docker-java</groupId> | |||
<artifactId>docker-java-transport-zerodep</artifactId> | |||
<version>3.2.6</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>redis.clients</groupId> | |||
<artifactId>jedis</artifactId> | |||
@@ -20,7 +20,7 @@ public class CloudRegionRelative extends CloudRegion { | |||
@Getter @Setter private double distance; | |||
public void setDistance(double latitude, double longitude) { | |||
distance = getLocation().distance(latitude, longitude); | |||
if (getLocation() != null) distance = getLocation().distance(latitude, longitude); | |||
} | |||
} |
@@ -20,4 +20,10 @@ public class PackerConfig { | |||
@Getter @Setter private JsonNode builder; | |||
@Getter @Setter private JsonNode post; | |||
public boolean hasPost () { return post != null; } | |||
@Getter @Setter private Boolean sudo; | |||
public boolean sudo () { return sudo == null || sudo; } | |||
} |
@@ -0,0 +1,172 @@ | |||
package bubble.cloud.compute.docker; | |||
import bubble.cloud.CloudRegion; | |||
import bubble.cloud.compute.*; | |||
import bubble.model.cloud.BubbleNode; | |||
import bubble.model.cloud.BubbleNodeState; | |||
import bubble.model.cloud.CloudCredentials; | |||
import com.github.dockerjava.api.DockerClient; | |||
import com.github.dockerjava.api.command.CreateContainerResponse; | |||
import com.github.dockerjava.api.command.InspectContainerResponse; | |||
import com.github.dockerjava.api.model.Container; | |||
import com.github.dockerjava.api.model.Image; | |||
import com.github.dockerjava.core.DefaultDockerClientConfig; | |||
import com.github.dockerjava.core.DockerClientConfig; | |||
import com.github.dockerjava.core.DockerClientImpl; | |||
import com.github.dockerjava.transport.DockerHttpClient; | |||
import com.github.dockerjava.zerodep.ZerodepDockerHttpClient; | |||
import edu.emory.mathcs.backport.java.util.Arrays; | |||
import lombok.Getter; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.cobbzilla.util.collection.MapBuilder; | |||
import java.io.IOException; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import static bubble.service.packer.PackerJob.PACKER_IMAGE_PREFIX; | |||
import static java.lang.Boolean.parseBoolean; | |||
import static java.util.concurrent.TimeUnit.SECONDS; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.*; | |||
import static org.cobbzilla.util.json.JsonUtil.json; | |||
import static org.cobbzilla.util.system.Sleep.sleep; | |||
@Slf4j | |||
public class DockerComputeDriver extends ComputeServiceDriverBase { | |||
public static final List<CloudRegion> CLOUD_REGIONS = Arrays.asList(new CloudRegion[]{ | |||
new CloudRegion().setName("local").setInternalName("local") | |||
}); | |||
public static final List<ComputeNodeSize> CLOUD_SIZES = Arrays.asList(new ComputeNodeSize[]{ | |||
new ComputeNodeSize().setName("local").setInternalName("local").setType(ComputeNodeSizeType.local) | |||
}); | |||
public static final List<OsImage> CLOUD_OS_IMAGES = Arrays.asList(new OsImage[]{ | |||
new OsImage().setName("ubuntu:20.04").setId("ubuntu:20.04").setRegion("local") | |||
}); | |||
public static final long START_TIMEOUT = SECONDS.toMillis(120); | |||
public static final String DEFAULT_HOST = "unix:///var/run/docker.sock"; | |||
private static final String LABEL_IMAGE = "bubble_image"; | |||
private static final String LABEL_CLOUD = "bubble_cloud"; | |||
private static final String LABEL_NODE = "bubble_node"; | |||
@Getter private final List<CloudRegion> cloudRegions = CLOUD_REGIONS; | |||
@Getter private final List<ComputeNodeSize> cloudSizes = CLOUD_SIZES; | |||
@Getter private final List<OsImage> cloudOsImages = CLOUD_OS_IMAGES; | |||
@Getter(lazy=true) private final DockerClient dockerClient = initDockerClient(); | |||
private DockerClient initDockerClient() { | |||
CloudCredentials creds = getCredentials(); | |||
if (creds == null) creds = new CloudCredentials(); | |||
final String host = creds.hasParam("host") ? creds.getParam("host") : DEFAULT_HOST; | |||
final boolean tlsVerify = creds.hasParam("tlsVerify") && parseBoolean(creds.getParam("tlsVerify")); | |||
final String certPath = creds.hasParam("certPath") ? creds.getParam("certPath") : null; | |||
final DockerClientConfig dockerConfig = DefaultDockerClientConfig.createDefaultConfigBuilder() | |||
.withDockerHost(host) | |||
.withDockerTlsVerify(tlsVerify) | |||
.withDockerCertPath(certPath) | |||
.withRegistryUsername(creds.getParam("registryUsername")) | |||
.withRegistryPassword(creds.getParam("registryPassword")) | |||
.withRegistryEmail(creds.getParam("registryEmail")) | |||
.withRegistryUrl(creds.getParam("registryUrl")) | |||
.build(); | |||
final DockerHttpClient client = new ZerodepDockerHttpClient.Builder() | |||
.dockerHost(dockerConfig.getDockerHost()) | |||
.build(); | |||
return DockerClientImpl.getInstance(dockerConfig, client); | |||
} | |||
@Override public BubbleNode cleanupStart(BubbleNode node) throws Exception { return node; } | |||
@Override public BubbleNode start(BubbleNode node) throws Exception { | |||
final DockerClient dc = getDockerClient(); | |||
final PackerImage packerImage = getOrCreatePackerImage(node); | |||
final CreateContainerResponse ccr = dc.createContainerCmd(packerImage.getId()) | |||
.withLabels(MapBuilder.build(new String[][] { | |||
{LABEL_CLOUD, cloud.getUuid()}, | |||
{LABEL_NODE, node.getUuid()} | |||
})) | |||
.exec(); | |||
final long start = now(); | |||
while (listNodes().stream().noneMatch(n -> n.isRunning() && n.getUuid().equals(node.getUuid()))) { | |||
if (now() - start > START_TIMEOUT) { | |||
return die("start("+node.id()+"): timeout"); | |||
} | |||
sleep(SECONDS.toMillis(5), "waiting for docker container to be running"); | |||
} | |||
return node; | |||
} | |||
private String lookupContainer(BubbleNode node) { | |||
final DockerClient dc = getDockerClient(); | |||
final List<Container> containers = dc.listContainersCmd() | |||
.withLabelFilter(MapBuilder.build(LABEL_NODE, node.getUuid())) | |||
.exec(); | |||
if (empty(containers)) return die("lookupContainer: node not found: "+node.getUuid()); | |||
if (containers.size() > 1) return die("lookupContainer: multiple containers found for node: "+node.getUuid()); | |||
return containers.get(0).getId(); | |||
} | |||
@Override public BubbleNode stop(BubbleNode node) throws Exception { | |||
final DockerClient dc = getDockerClient(); | |||
final String containerId = lookupContainer(node); | |||
dc.stopContainerCmd(containerId).exec(); | |||
return node; | |||
} | |||
@Override public BubbleNode status(BubbleNode node) throws Exception { | |||
final DockerClient dc = getDockerClient(); | |||
final String containerId = lookupContainer(node); | |||
final InspectContainerResponse status = dc.inspectContainerCmd(containerId).exec(); | |||
log.info("status("+node.id()+"): "+json(status)); | |||
final Boolean dead = status.getState().getDead(); | |||
if (dead != null && dead) return node.setState(BubbleNodeState.stopped); | |||
final Boolean running = status.getState().getRunning(); | |||
if (running != null && running) return node.setState(BubbleNodeState.running); | |||
log.warn("status("+node.id()+"): recognized state: "+json(status.getState())); | |||
return node; | |||
} | |||
@Override public List<PackerImage> getAllPackerImages() { | |||
final DockerClient dc = getDockerClient(); | |||
final List<Image> images = dc.listImagesCmd().withImageNameFilter(PACKER_IMAGE_PREFIX).withLabelFilter(MapBuilder.build(LABEL_IMAGE, PACKER_IMAGE_PREFIX)).exec(); | |||
final List<PackerImage> packerImages = new ArrayList<>(); | |||
for (Image i : images) { | |||
final PackerImage p = new PackerImage(); | |||
p.setId(i.getId()); | |||
p.setName(empty(i.getLabels()) ? i.getId() : i.getLabels().size() == 1 ? i.getLabels().values().iterator().next() : json(i.getLabels())); | |||
p.setRegions(null); | |||
packerImages.add(p); | |||
} | |||
return packerImages; | |||
} | |||
@Override public List<PackerImage> getPackerImagesForRegion(String region) { return getAllPackerImages(); } | |||
@Override public List<BubbleNode> listNodes() throws IOException { | |||
final DockerClient dc = getDockerClient(); | |||
final List<BubbleNode> nodes = new ArrayList<>(); | |||
final List<Container> containers = dc.listContainersCmd() | |||
.withLabelFilter(MapBuilder.build(LABEL_CLOUD, cloud.getUuid())) | |||
.exec(); | |||
for (Container c : containers) { | |||
final BubbleNode n = new BubbleNode().setState(BubbleNodeState.running); | |||
n.setUuid(c.getLabels().get(LABEL_NODE)); | |||
n.setCloud(c.getLabels().get(LABEL_CLOUD)); | |||
nodes.add(n); | |||
} | |||
return nodes; | |||
} | |||
} |
@@ -49,10 +49,14 @@ public class GeoLocation { | |||
} | |||
public double distance(double lat, double lon) { | |||
if (lat < 0 || lon < 0) return -1.0; | |||
double thisLat = big(getLat()).doubleValue(); | |||
double thisLon = big(getLon()).doubleValue(); | |||
if (thisLat < 0 || thisLon < 0) return -1.0; | |||
return Haversine.distance( | |||
big(getLat()).doubleValue(), | |||
thisLat, | |||
lat, | |||
big(getLon()).doubleValue(), | |||
thisLon, | |||
lon); | |||
} | |||
@@ -41,7 +41,6 @@ import org.glassfish.grizzly.http.server.Request; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Repository; | |||
import javax.annotation.Nullable; | |||
import javax.transaction.Transactional; | |||
import java.util.Collection; | |||
import java.util.HashMap; | |||
@@ -359,7 +358,7 @@ public class AccountDAO extends AbstractCRUDDAO<Account> implements SqlViewSearc | |||
private final String NETWORK_OWNER_ACCOUNT_UUID_PARAM = "__thisNetworkOwnerAccountUuid__"; | |||
@Override public int bulkDeleteWhere(@NonNull final String whereClause, | |||
@Nullable final Map<String, Object> parameters) { | |||
final Map<String, Object> parameters) { | |||
final Map<String, Object> enhancedParams = parameters != null ? parameters : new HashMap<>(); | |||
enhancedParams.put(NETWORK_OWNER_ACCOUNT_UUID_PARAM, configuration.getThisNetwork().getAccount()); | |||
@@ -367,7 +366,7 @@ public class AccountDAO extends AbstractCRUDDAO<Account> implements SqlViewSearc | |||
enhancedParams); | |||
} | |||
@Override public void delete(@Nullable final Collection<Account> accounts) { | |||
@Override public void delete(final Collection<Account> accounts) { | |||
if (empty(accounts)) return; | |||
final var networkOwnerUuid = configuration.getThisNetwork().getAccount(); | |||
if (accounts.removeIf(a -> a != null && a.getUuid().equals(networkOwnerUuid))) { | |||
@@ -19,7 +19,6 @@ import org.hibernate.criterion.Order; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Repository; | |||
import javax.annotation.Nullable; | |||
import javax.transaction.Transactional; | |||
import java.util.ArrayList; | |||
import java.util.Collection; | |||
@@ -161,7 +160,7 @@ public class CloudServiceDAO extends AccountOwnedTemplateDAO<CloudService> { | |||
return !findPublicTemplatesByType(admin.getUuid(), CloudServiceType.payment).isEmpty(); | |||
} | |||
@Override public int bulkDeleteWhere(@NonNull String whereClause, @Nullable Map<String, Object> parameters) { | |||
@Override public int bulkDeleteWhere(@NonNull String whereClause, Map<String, Object> parameters) { | |||
// TODO for these maybe an outside cron would be better solution. BulkDelete is used here to be fast. | |||
// For now this postServiceDelete is called within a single place where this method is used - Account Deletion. | |||
log.warn("Not calling postServiceDelete for services deleted in this way"); | |||
@@ -59,10 +59,10 @@ public interface RegionalServiceDriver { | |||
} | |||
final CloudRegionRelative r = new CloudRegionRelative(region); | |||
r.setCloud(c.getUuid()); | |||
if (latLonIsValid) { | |||
if (latLonIsValid && latitude >= 0 && longitude >= 0) { | |||
r.setDistance(latitude, longitude); | |||
} else { | |||
r.setDistance(-1); | |||
r.setDistance(0); | |||
} | |||
allRegions.add(r); | |||
} | |||
@@ -24,7 +24,6 @@ import org.springframework.context.ApplicationContext; | |||
import org.springframework.stereotype.Repository; | |||
import org.springframework.stereotype.Service; | |||
import javax.annotation.Nullable; | |||
import javax.validation.Valid; | |||
import javax.ws.rs.*; | |||
import javax.ws.rs.core.Context; | |||
@@ -153,7 +152,7 @@ public class DebugResource { | |||
) | |||
public Response echoJsonInLog(@Context ContainerRequest ctx, | |||
@Valid @NonNull final JsonNode input, | |||
@QueryParam("respondWith") @Nullable final String respondWith) throws IOException { | |||
@QueryParam("respondWith") final String respondWith) throws IOException { | |||
final var output = "ECHO: \n" + toJsonOrDie(input); | |||
log.info(output); | |||
@@ -58,7 +58,6 @@ import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Service; | |||
import org.springframework.transaction.annotation.Transactional; | |||
import javax.annotation.Nullable; | |||
import javax.validation.Valid; | |||
import javax.ws.rs.*; | |||
import javax.ws.rs.core.Context; | |||
@@ -238,7 +237,7 @@ public class AuthResource { | |||
@Autowired private SageHelloService sageHelloService; | |||
@Autowired private RestoreService restoreService; | |||
@NonNull private BubbleNode checkRestoreRequest(@Nullable final String restoreKey) { | |||
@NonNull private BubbleNode checkRestoreRequest(final String restoreKey) { | |||
if (restoreKey == null) throw invalidEx("err.restoreKey.required"); | |||
// ensure we have been initialized | |||
@@ -271,7 +270,7 @@ public class AuthResource { | |||
) | |||
public Response restore(@NonNull @Context final Request req, | |||
@NonNull @Context final ContainerRequest ctx, | |||
@Nullable @PathParam("restoreKey") final String restoreKey, | |||
@PathParam("restoreKey") final String restoreKey, | |||
@NonNull @Valid final NetworkKeys.EncryptedNetworkKeys encryptedKeys) { | |||
final var sageNode = checkRestoreRequest(restoreKey); | |||
@@ -14,7 +14,6 @@ import lombok.NonNull; | |||
import org.glassfish.jersey.server.ContainerRequest; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import javax.annotation.Nullable; | |||
import javax.ws.rs.*; | |||
import javax.ws.rs.core.Context; | |||
import javax.ws.rs.core.Response; | |||
@@ -69,7 +68,7 @@ public class LogsResource { | |||
responses=@ApiResponse(responseCode=SC_OK, description="empty response indicates success") | |||
) | |||
@NonNull public Response startLogging(@NonNull @Context final ContainerRequest ctx, | |||
@Nullable @QueryParam("ttlDays") final Byte ttlDays) { | |||
@QueryParam("ttlDays") final Byte ttlDays) { | |||
final Account caller = userPrincipal(ctx); | |||
if (!caller.admin()) throw forbiddenEx(); | |||
return setLogFlag(true, Optional.ofNullable(ttlDays)); | |||
@@ -26,7 +26,6 @@ import org.glassfish.grizzly.http.server.Request; | |||
import org.glassfish.jersey.server.ContainerRequest; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import javax.annotation.Nullable; | |||
import javax.ws.rs.*; | |||
import javax.ws.rs.core.Context; | |||
import javax.ws.rs.core.Response; | |||
@@ -78,7 +77,7 @@ public class NetworkBackupKeysResource { | |||
return ok(); | |||
} | |||
@NonNull private String fetchAndCheckEncryptionKey(@Nullable final NameAndValue enc) { | |||
@NonNull private String fetchAndCheckEncryptionKey(final NameAndValue enc) { | |||
final String encryptionKey = enc == null ? null : enc.getValue(); | |||
final ConstraintViolationBean error = validatePassword(encryptionKey); | |||
if (error != null) throw new SimpleViolationException(error); | |||
@@ -95,7 +94,7 @@ public class NetworkBackupKeysResource { | |||
@NonNull public Response retrieveNetworkKeys(@NonNull @Context final Request req, | |||
@NonNull @Context final ContainerRequest ctx, | |||
@NonNull @PathParam("keysCode") final String keysCode, | |||
@Nullable final NameAndValue enc) { | |||
final NameAndValue enc) { | |||
final var encryptionKey = fetchAndCheckEncryptionKey(enc); | |||
final var networkKeys = keysService.retrieveKeys(keysCode); | |||
return ok(networkKeys.encrypt(encryptionKey)); | |||
@@ -115,7 +114,7 @@ public class NetworkBackupKeysResource { | |||
@NonNull public Response backupDownloadStart(@NonNull @Context final ContainerRequest ctx, | |||
@NonNull @PathParam("keysCode") final String keysCode, | |||
@NonNull @QueryParam("backupId") final String backupId, | |||
@Nullable final NameAndValue enc) { | |||
final NameAndValue enc) { | |||
final var passphrase = fetchAndCheckEncryptionKey(enc); | |||
keysService.retrieveKeys(keysCode); | |||
@@ -22,7 +22,6 @@ import org.cobbzilla.wizard.cache.redis.RedisService; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Service; | |||
import javax.annotation.Nullable; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
@@ -117,7 +116,7 @@ public class RestoreService { | |||
} | |||
} | |||
@Nullable private String checkAndGetKeyJson(@NonNull final String restoreKey) { | |||
private String checkAndGetKeyJson(@NonNull final String restoreKey) { | |||
final String keyJson = getRestoreKeys().get(restoreKey); | |||
if (keyJson == null) { | |||
log.error("restore: restoreKey not found: " + restoreKey); | |||
@@ -126,7 +125,7 @@ public class RestoreService { | |||
return keyJson; | |||
} | |||
@Nullable private String checkAndGetRestoreDirPath() { | |||
private String checkAndGetRestoreDirPath() { | |||
final var existingFiles = RESTORE_DIR.list(); | |||
final var restoreDirAbs = abs(RESTORE_DIR); | |||
@@ -14,10 +14,12 @@ import bubble.cloud.geoLocation.GeoLocation; | |||
import bubble.dao.account.AccountDAO; | |||
import bubble.model.account.Account; | |||
import bubble.model.cloud.AnsibleInstallType; | |||
import bubble.model.cloud.CloudCredentials; | |||
import bubble.model.cloud.CloudService; | |||
import bubble.server.BubbleConfiguration; | |||
import bubble.server.SoftwareVersions; | |||
import bubble.service.cloud.GeoService; | |||
import com.fasterxml.jackson.databind.JsonNode; | |||
import lombok.Cleanup; | |||
import lombok.Getter; | |||
import lombok.extern.slf4j.Slf4j; | |||
@@ -79,6 +81,8 @@ public class PackerJob implements Callable<List<PackerImage>> { | |||
public static final String BUILD_REGION_VAR = "buildRegion"; | |||
public static final String IMAGE_REGIONS_VAR = "imageRegions"; | |||
public static final String BUILDERS_VAR = "builders"; | |||
public static final String POST_PROCESSOR_VAR = "postProcessor"; | |||
public static final String SUDO_VAR = "sudo"; | |||
public static final String PACKER_PLAYBOOK_TEMPLATE = "packer-playbook.yml.hbs"; | |||
public static final String PACKER_PLAYBOOK = "packer-playbook.yml"; | |||
public static final String PACKER_BINARY = System.getProperty("user.home")+"/packer/packer"; | |||
@@ -146,7 +150,8 @@ public class PackerJob implements Callable<List<PackerImage>> { | |||
// create handlebars context | |||
final Map<String, Object> ctx = new HashMap<>(); | |||
ctx.put("credentials", NameAndValue.toMap(cloud.getCredentials().getParams())); | |||
final CloudCredentials creds = cloud.getCredentials(); | |||
ctx.put("credentials", creds == null ? Collections.emptyMap() : NameAndValue.toMap(creds.getParams())); | |||
ctx.put("compute", computeDriver); | |||
ctx.put("sizes", computeDriver.getSizesMap()); | |||
ctx.put("os", computeDriver.getOs()); | |||
@@ -167,6 +172,15 @@ public class PackerJob implements Callable<List<PackerImage>> { | |||
env.put(variable.getName(), HandlebarsUtil.apply(configuration.getHandlebars(), variable.getValue(), ctx, '[', ']')); | |||
} | |||
if (!env.containsKey("HOME")) env.put("HOME", HOME_DIR); | |||
// Docker builder requires "docker" command to be on our path | |||
// It is usually in /usr/local/bin | |||
// May need to make this more flexible if docker is elsewhere, or other tools/paths are needed | |||
if (env.containsKey("PATH")) { | |||
env.put("PATH", "${PATH}:/usr/local/bin"); | |||
} else { | |||
env.put("PATH", "/usr/local/bin"); | |||
} | |||
ctx.put(VARIABLES_VAR, packerConfig.getVars()); | |||
// copy ansible and other packer files to temp dir | |||
@@ -254,6 +268,9 @@ public class PackerJob implements Callable<List<PackerImage>> { | |||
builderJsons.add(generateBuilder(packerConfig, ctx)); | |||
} | |||
ctx.put(BUILDERS_VAR, builderJsons); | |||
ctx.put(SUDO_VAR, packerConfig.sudo()); | |||
if (packerConfig.hasPost()) ctx.put(POST_PROCESSOR_VAR, generatePostProcessor(packerConfig, ctx)); | |||
// write playbook file | |||
final String playbookTemplate = FileUtil.toString(abs(tempDir)+ "/" + PACKER_PLAYBOOK_TEMPLATE); | |||
@@ -333,7 +350,15 @@ public class PackerJob implements Callable<List<PackerImage>> { | |||
} | |||
public String generateBuilder(PackerConfig packerConfig, Map<String, Object> ctx) { | |||
return HandlebarsUtil.apply(configuration.getHandlebars(), json(packerConfig.getBuilder()), ctx, '<', '>') | |||
return appyHandlebars(ctx, packerConfig.getBuilder()); | |||
} | |||
public String generatePostProcessor(PackerConfig packerConfig, Map<String, Object> ctx) { | |||
return packerConfig.hasPost() ? appyHandlebars(ctx, packerConfig.getPost()) : null; | |||
} | |||
private String appyHandlebars(Map<String, Object> ctx, JsonNode thing) { | |||
return HandlebarsUtil.apply(configuration.getHandlebars(), json(thing), ctx, '<', '>') | |||
.replace("[[", "{{") | |||
.replace("]]", "}}"); | |||
} | |||
@@ -22,7 +22,6 @@ import org.cobbzilla.wizard.cache.redis.RedisService; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Service; | |||
import javax.annotation.Nullable; | |||
import java.util.HashSet; | |||
import java.util.List; | |||
import java.util.Map; | |||
@@ -115,7 +114,7 @@ public class StandardAppPrimerService implements AppPrimerService { | |||
getPrimerThread().submit(() -> _prime(account, singleApp)); | |||
} | |||
private synchronized void _prime(@NonNull final Account account, @Nullable final BubbleApp singleApp) { | |||
private synchronized void _prime(@NonNull final Account account, final BubbleApp singleApp) { | |||
try { | |||
final List<Device> devices = deviceDAO.findByAccount(account.getUuid()); | |||
if (devices.isEmpty()) return; | |||
@@ -151,80 +151,117 @@ | |||
"template": true | |||
}, | |||
{ | |||
"name": "DockerCompute", | |||
"type": "compute", | |||
"driverClass": "bubble.cloud.compute.docker.DockerComputeDriver", | |||
"driverConfig": { | |||
"regions": [{"name": "local", "internalName": "local"}], | |||
"sizes": [{"name": "local", "type": "local", "internalName": "local"}], | |||
"os": "ubuntu:20.04", | |||
"packer": { | |||
"vars": [], | |||
"sudo": false, | |||
"builder": { | |||
"type": "docker", | |||
"image": "<<os.name>>", | |||
"export_path": "<<packerImageName>>.tar", | |||
"changes": [ | |||
"LABEL bubble_image=<<packerImageName>>", | |||
"EXPOSE 80 443 1202" | |||
] | |||
}, | |||
"post": { | |||
"type": "docker-import", | |||
"repository": "local/bubble", | |||
"tag": "<<packerImageName>>" | |||
} | |||
} | |||
}, | |||
"credentials": { | |||
"params": [ | |||
{"name": "host", "value": "unix:///var/run/docker.sock"} | |||
] | |||
}, | |||
"template": false | |||
}, | |||
{ | |||
"name": "VultrCompute", | |||
"type": "compute", | |||
"driverClass": "bubble.cloud.compute.vultr.VultrDriver", | |||
"driverConfig": { | |||
"regions": [{ | |||
"name": "Vultr - Dallas", | |||
"internalName": "Dallas", | |||
"location": {"city": "Dallas", "country": "US", "region": "TX", "lat": "32.779167", "lon": "-96.808889"} | |||
}, { | |||
"name": "Vultr - Los Angeles", | |||
"internalName": "Los Angeles", | |||
"location": {"city": "Los Angeles", "country": "US", "region": "CA", "lat": "34.05", "lon": "-118.25"} | |||
}, { | |||
"name": "Vultr - Miami", | |||
"internalName": "Miami", | |||
"location": {"city": "Miami", "country": "US", "region": "FL", "lat": "25.775278", "lon": "-80.208889"} | |||
}, { | |||
"name": "Vultr - Seattle", | |||
"internalName": "Seattle", | |||
"location": {"city": "Seattle", "country": "US", "region": "WA", "lat": "47.609722", "lon": "-122.333056"} | |||
}, { | |||
"name": "Vultr - New Jersey", | |||
"internalName": "New Jersey", | |||
"location": {"city": "Newark", "country": "US", "region": "NJ", "lat": "40.72", "lon": "-74.17"} | |||
}, { | |||
"name": "Vultr - Atlanta", | |||
"internalName": "Atlanta", | |||
"location": {"city": "Atlanta", "country": "US", "region": "GA", "lat": "33.755", "lon": "-84.39"} | |||
}, { | |||
"name": "Vultr - Chicago", | |||
"internalName": "Chicago", | |||
"location": {"city": "Chicago", "country": "US", "region": "IL", "lat": "41.881944", "lon": "-87.627778"} | |||
}, { | |||
"name": "Vultr - San Jose", | |||
"internalName": "Silicon Valley", | |||
"location": {"city": "San Jose", "country": "US", "region": "CA", "lat": "37.333333", "lon": "-121.9"} | |||
}, { | |||
"name": "Vultr - Toronto", | |||
"internalName": "Toronto", | |||
"location": {"city": "Toronto", "country": "CA", "region": "ON", "lat": "43.741667", "lon": "-79.373333"} | |||
}, { | |||
"name": "Vultr - London", | |||
"internalName": "London", | |||
"location": {"city": "London", "country": "GB", "region": "London", "lat": "51.507222", "lon": "-0.1275"} | |||
}, { | |||
"name": "Vultr - Paris", | |||
"internalName": "Paris", | |||
"location": {"city": "Paris", "country": "FR", "region": "Ile-de-Paris", "lat": "48.8567", "lon": "2.3508"} | |||
}, { | |||
"name": "Vultr - Frankfurt", | |||
"internalName": "Frankfurt", | |||
"location": {"city": "Frankfurt", "country": "DE", "region": "Hesse", "lat": "50.116667", "lon": "8.683333"} | |||
}, { | |||
"name": "Vultr - Singapore", | |||
"internalName": "Singapore", | |||
"location": {"city": "Singapore", "country": "SG", "region": "Singapore", "lat": "1.283333", "lon": "103.833333"} | |||
}, { | |||
"name": "Vultr - Tokyo", | |||
"internalName": "Tokyo", | |||
"location": {"city": "Tokyo", "country": "JP", "region": "Kantō", "lat": "35.689722", "lon": "139.692222"} | |||
}, { | |||
"name": "Vultr - Seoul", | |||
"internalName": "Seoul", | |||
"location": {"city": "Seoul", "country": "KR", "region": "Sudogwon", "lat": "37.566667", "lon": "126.966667"} | |||
}, { | |||
"name": "Vultr - Sydney", | |||
"internalName": "Sydney", | |||
"location": {"city": "Sydney", "country": "AU", "region": "NSW", "lat": "-33.865", "lon": "151.209444"} | |||
}, { | |||
"name": "Vultr - Amsterdam", | |||
"internalName": "ams3", | |||
"location": {"city": "Amsterdam", "country": "NL", "region": "North Holland", "lat": "52.366667", "lon": "4.9"} | |||
}], | |||
"regions": [ | |||
{ | |||
"name": "Vultr - Dallas", | |||
"internalName": "Dallas", | |||
"location": {"city": "Dallas", "country": "US", "region": "TX", "lat": "32.779167", "lon": "-96.808889"} | |||
}, { | |||
"name": "Vultr - Los Angeles", | |||
"internalName": "Los Angeles", | |||
"location": {"city": "Los Angeles", "country": "US", "region": "CA", "lat": "34.05", "lon": "-118.25"} | |||
}, { | |||
"name": "Vultr - Miami", | |||
"internalName": "Miami", | |||
"location": {"city": "Miami", "country": "US", "region": "FL", "lat": "25.775278", "lon": "-80.208889"} | |||
}, { | |||
"name": "Vultr - Seattle", | |||
"internalName": "Seattle", | |||
"location": {"city": "Seattle", "country": "US", "region": "WA", "lat": "47.609722", "lon": "-122.333056"} | |||
}, { | |||
"name": "Vultr - New Jersey", | |||
"internalName": "New Jersey", | |||
"location": {"city": "Newark", "country": "US", "region": "NJ", "lat": "40.72", "lon": "-74.17"} | |||
}, { | |||
"name": "Vultr - Atlanta", | |||
"internalName": "Atlanta", | |||
"location": {"city": "Atlanta", "country": "US", "region": "GA", "lat": "33.755", "lon": "-84.39"} | |||
}, { | |||
"name": "Vultr - Chicago", | |||
"internalName": "Chicago", | |||
"location": {"city": "Chicago", "country": "US", "region": "IL", "lat": "41.881944", "lon": "-87.627778"} | |||
}, { | |||
"name": "Vultr - San Jose", | |||
"internalName": "Silicon Valley", | |||
"location": {"city": "San Jose", "country": "US", "region": "CA", "lat": "37.333333", "lon": "-121.9"} | |||
}, { | |||
"name": "Vultr - Toronto", | |||
"internalName": "Toronto", | |||
"location": {"city": "Toronto", "country": "CA", "region": "ON", "lat": "43.741667", "lon": "-79.373333"} | |||
}, { | |||
"name": "Vultr - London", | |||
"internalName": "London", | |||
"location": {"city": "London", "country": "GB", "region": "London", "lat": "51.507222", "lon": "-0.1275"} | |||
}, { | |||
"name": "Vultr - Paris", | |||
"internalName": "Paris", | |||
"location": {"city": "Paris", "country": "FR", "region": "Ile-de-Paris", "lat": "48.8567", "lon": "2.3508"} | |||
}, { | |||
"name": "Vultr - Frankfurt", | |||
"internalName": "Frankfurt", | |||
"location": {"city": "Frankfurt", "country": "DE", "region": "Hesse", "lat": "50.116667", "lon": "8.683333"} | |||
}, { | |||
"name": "Vultr - Singapore", | |||
"internalName": "Singapore", | |||
"location": {"city": "Singapore", "country": "SG", "region": "Singapore", "lat": "1.283333", "lon": "103.833333"} | |||
}, { | |||
"name": "Vultr - Tokyo", | |||
"internalName": "Tokyo", | |||
"location": {"city": "Tokyo", "country": "JP", "region": "Kantō", "lat": "35.689722", "lon": "139.692222"} | |||
}, { | |||
"name": "Vultr - Seoul", | |||
"internalName": "Seoul", | |||
"location": {"city": "Seoul", "country": "KR", "region": "Sudogwon", "lat": "37.566667", "lon": "126.966667"} | |||
}, { | |||
"name": "Vultr - Sydney", | |||
"internalName": "Sydney", | |||
"location": {"city": "Sydney", "country": "AU", "region": "NSW", "lat": "-33.865", "lon": "151.209444"} | |||
}, { | |||
"name": "Vultr - Amsterdam", | |||
"internalName": "ams3", | |||
"location": {"city": "Amsterdam", "country": "NL", "region": "North Holland", "lat": "52.366667", "lon": "4.9"} | |||
} | |||
], | |||
"sizes": [ | |||
{"name": "small", "type": "small", "internalName": "1024 MB RAM,25 GB SSD,1.00 TB BW", "vcpu": 1, "memoryMB": 1024, "diskGB": 25}, | |||
{"name": "medium", "type": "medium", "internalName": "2048 MB RAM,55 GB SSD,2.00 TB BW", "vcpu": 1, "memoryMB": 2048, "diskGB": 55}, | |||
@@ -331,55 +368,57 @@ | |||
"type": "compute", | |||
"driverClass": "bubble.cloud.compute.ec2.AmazonEC2Driver", | |||
"driverConfig": { | |||
"regions": [{ | |||
"name": "Amazon - N. Virginia", "description": "US East 1 (N. Virginia)", "internalName": "us-east-1", | |||
"location": {"city": "Arlington", "region": "VA", "country": "US", "lat": "38.880278", "lon": "-77.108333"} | |||
}, { | |||
"name": "Amazon - Ohio", "description": "US East 2 (Ohio)", "internalName": "us-east-2", | |||
"location": {"region": "OH", "country": "US", "lat": "40.3416167", "lon": "-84.9180579"} | |||
}, { | |||
"name": "Amazon - N. California", "description": "US West 1 (N. California)", "internalName": "us-west-1", | |||
"location": {"city": "San Jose", "country": "US", "region": "CA", "lat": "37.333333", "lon": "-121.9"} | |||
}, { | |||
"name": "Amazon - Oregon", "description": "US West 2 (Oregon)", "internalName": "us-west-2", | |||
"location": {"city": "Hermiston", "region": "OR", "country": "US", "lat": "45.841111", "lon": "-119.291667"} | |||
}, { | |||
"name": "Amazon - Canada", "description": "Canada (Central)", "internalName": "ca-central-1", | |||
"location": {"region": "QC", "country": "CA", "lat": "46.813889", "lon": "-71.208056"} | |||
}, { | |||
"name": "Amazon - Stockholm", "description": "EU (Stockholm)", "internalName": "eu-north-1", | |||
"location": {"city": "Stockholm", "region": "Södermanland", "country": "SE", "lat": "59.329444", "lon": "18.068611"} | |||
}, { | |||
"name": "Amazon - Ireland", "description": "EU (Ireland)", "internalName": "eu-west-1", | |||
"location": {"city": "Dublin", "region": "Leinster", "country": "IE", "lat": "53.35", "lon": "-6.266667"} | |||
}, { | |||
"name": "Amazon - London", "description": "EU (London)", "internalName": "eu-west-2", | |||
"location": {"city": "London", "country": "GB", "region": "London", "lat": "51.507222", "lon": "-0.1275"} | |||
}, { | |||
"name": "Amazon - Milan", "description": "EU (Milan)", "internalName": "eu-west-3", | |||
"location": {"city": "Milan", "country": "IT", "region": "Lombardy", "lat": "45.466944", "lon": "9.19"} | |||
}, { | |||
"name": "Amazon - Frankfurt", "description": "EU (Frankfurt)", "internalName": "eu-central-1", | |||
"location": {"city": "Frankfurt", "country": "DE", "region": "Hesse", "lat": "50.116667", "lon": "8.683333"} | |||
}, { | |||
"name": "Amazon - Tokyo", "description": "Asia Pacific (Tokyo)", "internalName": "ap-northeast-1", | |||
"location": {"city": "Tokyo", "country": "JP", "region": "Kantō", "lat": "35.689722", "lon": "139.692222"} | |||
}, { | |||
"name": "Amazon - Seoul", "description": "Asia Pacific (Seoul)", "internalName": "ap-northeast-2", | |||
"location": {"city": "Seoul", "country": "KR", "region": "Sudogwon", "lat": "37.566667", "lon": "126.966667"} | |||
}, { | |||
"name": "Amazon - Singapore", "description": "Asia Pacific (Singapore)", "internalName": "ap-southeast-1", | |||
"location": {"city": "Singapore", "country": "SG", "region": "Singapore", "lat": "1.283333", "lon": "103.833333"} | |||
}, { | |||
"name": "Amazon - Sydney", "description": "Asia Pacific (Sydney)", "internalName": "ap-southeast-2", | |||
"location": {"city": "Sydney", "country": "AU", "region": "NSW", "lat": "-33.865", "lon": "151.209444"} | |||
}, { | |||
"name": "Amazon - Mumbai", "description": "Asia Pacific (Mumbai)", "internalName": "ap-south-1", | |||
"location": {"city": "Mumbai", "country": "IN", "region": "Konkan", "lat": "18.975", "lon": "72.825833"} | |||
}, { | |||
"name": "Amazon - São Paulo", "description": "South America (São Paulo)", "internalName": "sa-east-1", | |||
"location": {"city": "São Paulo", "country": "BR", "region": "São Paulo", "lat": "-23.55", "lon": "-46.633333"} | |||
}], | |||
"regions": [ | |||
{ | |||
"name": "Amazon - N. Virginia", "description": "US East 1 (N. Virginia)", "internalName": "us-east-1", | |||
"location": {"city": "Arlington", "region": "VA", "country": "US", "lat": "38.880278", "lon": "-77.108333"} | |||
}, { | |||
"name": "Amazon - Ohio", "description": "US East 2 (Ohio)", "internalName": "us-east-2", | |||
"location": {"region": "OH", "country": "US", "lat": "40.3416167", "lon": "-84.9180579"} | |||
}, { | |||
"name": "Amazon - N. California", "description": "US West 1 (N. California)", "internalName": "us-west-1", | |||
"location": {"city": "San Jose", "country": "US", "region": "CA", "lat": "37.333333", "lon": "-121.9"} | |||
}, { | |||
"name": "Amazon - Oregon", "description": "US West 2 (Oregon)", "internalName": "us-west-2", | |||
"location": {"city": "Hermiston", "region": "OR", "country": "US", "lat": "45.841111", "lon": "-119.291667"} | |||
}, { | |||
"name": "Amazon - Canada", "description": "Canada (Central)", "internalName": "ca-central-1", | |||
"location": {"region": "QC", "country": "CA", "lat": "46.813889", "lon": "-71.208056"} | |||
}, { | |||
"name": "Amazon - Stockholm", "description": "EU (Stockholm)", "internalName": "eu-north-1", | |||
"location": {"city": "Stockholm", "region": "Södermanland", "country": "SE", "lat": "59.329444", "lon": "18.068611"} | |||
}, { | |||
"name": "Amazon - Ireland", "description": "EU (Ireland)", "internalName": "eu-west-1", | |||
"location": {"city": "Dublin", "region": "Leinster", "country": "IE", "lat": "53.35", "lon": "-6.266667"} | |||
}, { | |||
"name": "Amazon - London", "description": "EU (London)", "internalName": "eu-west-2", | |||
"location": {"city": "London", "country": "GB", "region": "London", "lat": "51.507222", "lon": "-0.1275"} | |||
}, { | |||
"name": "Amazon - Milan", "description": "EU (Milan)", "internalName": "eu-west-3", | |||
"location": {"city": "Milan", "country": "IT", "region": "Lombardy", "lat": "45.466944", "lon": "9.19"} | |||
}, { | |||
"name": "Amazon - Frankfurt", "description": "EU (Frankfurt)", "internalName": "eu-central-1", | |||
"location": {"city": "Frankfurt", "country": "DE", "region": "Hesse", "lat": "50.116667", "lon": "8.683333"} | |||
}, { | |||
"name": "Amazon - Tokyo", "description": "Asia Pacific (Tokyo)", "internalName": "ap-northeast-1", | |||
"location": {"city": "Tokyo", "country": "JP", "region": "Kantō", "lat": "35.689722", "lon": "139.692222"} | |||
}, { | |||
"name": "Amazon - Seoul", "description": "Asia Pacific (Seoul)", "internalName": "ap-northeast-2", | |||
"location": {"city": "Seoul", "country": "KR", "region": "Sudogwon", "lat": "37.566667", "lon": "126.966667"} | |||
}, { | |||
"name": "Amazon - Singapore", "description": "Asia Pacific (Singapore)", "internalName": "ap-southeast-1", | |||
"location": {"city": "Singapore", "country": "SG", "region": "Singapore", "lat": "1.283333", "lon": "103.833333"} | |||
}, { | |||
"name": "Amazon - Sydney", "description": "Asia Pacific (Sydney)", "internalName": "ap-southeast-2", | |||
"location": {"city": "Sydney", "country": "AU", "region": "NSW", "lat": "-33.865", "lon": "151.209444"} | |||
}, { | |||
"name": "Amazon - Mumbai", "description": "Asia Pacific (Mumbai)", "internalName": "ap-south-1", | |||
"location": {"city": "Mumbai", "country": "IN", "region": "Konkan", "lat": "18.975", "lon": "72.825833"} | |||
}, { | |||
"name": "Amazon - São Paulo", "description": "South America (São Paulo)", "internalName": "sa-east-1", | |||
"location": {"city": "São Paulo", "country": "BR", "region": "São Paulo", "lat": "-23.55", "lon": "-46.633333"} | |||
} | |||
], | |||
"sizes": [ | |||
{"name": "small", "type": "small", "internalName": "t2.micro", "vcpu": 1, "memoryMB": 1024, "diskGB": 10, "diskType": "ebs_magnetic"}, | |||
{"name": "medium", "type": "medium", "internalName": "t2.small", "vcpu": 1, "memoryMB": 2048, "diskGB": 20, "diskType": "ebs_magnetic"}, | |||
@@ -12,10 +12,10 @@ | |||
"type": "shell", | |||
"inline": [ | |||
"sleep 30", | |||
"sudo bash -c 'DEBIAN_FRONTEND=noninteractive apt-get -y update'", | |||
"sudo bash -c 'DEBIAN_FRONTEND=noninteractive apt-get -y upgrade'", | |||
"sudo bash -c 'DEBIAN_FRONTEND=noninteractive apt-get -y install python3 python3-pip virtualenv'", | |||
"sudo pip3 install setuptools psycopg2-binary ansible" | |||
"[[#if sudo]]sudo [[/if]]bash -c 'DEBIAN_FRONTEND=noninteractive apt-get -y update'", | |||
"[[#if sudo]]sudo [[/if]]bash -c 'DEBIAN_FRONTEND=noninteractive apt-get -y upgrade'", | |||
"[[#if sudo]]sudo [[/if]]bash -c 'DEBIAN_FRONTEND=noninteractive apt-get -y install python3 python3-pip virtualenv'", | |||
"[[#if sudo]]sudo [[/if]]pip3 install setuptools psycopg2-binary ansible" | |||
] | |||
}, | |||
{ | |||
@@ -26,6 +26,7 @@ | |||
} | |||
], | |||
"post-processors": [ | |||
[[#if postProcessor]][[[postProcessor]]],[[/if]] | |||
{ | |||
"type": "manifest", | |||
"output": "manifest.json" | |||
@@ -27,6 +27,14 @@ | |||
src: dot-screenrc | |||
dest: /root/.screenrc | |||
- name: Ensure /root/.ssh exists | |||
file: | |||
path: /root/.ssh | |||
owner: root | |||
group: root | |||
mode: 0700 | |||
state: directory | |||
- name: Install packer key as only authorized key | |||
copy: | |||
src: packer_rsa | |||
@@ -51,13 +59,6 @@ | |||
group: root | |||
mode: 0500 | |||
- name: Start common services | |||
service: | |||
name: '{{ item }}' | |||
state: restarted | |||
with_items: | |||
- fail2ban | |||
- name: Create bubble-log group | |||
group: | |||
name: bubble-log | |||
@@ -118,6 +118,41 @@ | |||
"template": true | |||
}, | |||
{ | |||
"name": "DockerCompute", | |||
"type": "compute", | |||
"driverClass": "bubble.cloud.compute.docker.DockerComputeDriver", | |||
"driverConfig": { | |||
"regions": [{"name": "local", "internalName": "local"}], | |||
"sizes": [{"name": "local", "type": "local", "internalName": "local"}], | |||
"os": "ubuntu:20.04", | |||
"packer": { | |||
"vars": [], | |||
"sudo": false, | |||
"builder": { | |||
"type": "docker", | |||
"image": "<<os.name>>", | |||
"export_path": "<<packerImageName>>.tar", | |||
"changes": [ | |||
"LABEL bubble_image=<<packerImageName>>", | |||
"EXPOSE 80 443 1202" | |||
] | |||
}, | |||
"post": { | |||
"type": "docker-import", | |||
"repository": "local/bubble", | |||
"tag": "<<packerImageName>>" | |||
} | |||
} | |||
}, | |||
"credentials": { | |||
"params": [ | |||
{"name": "host", "value": "unix:///var/run/docker.sock"} | |||
] | |||
}, | |||
"template": false | |||
}, | |||
{ | |||
"_subst": true, | |||
"name": "VultrCompute", | |||
@@ -196,7 +231,7 @@ | |||
], | |||
"config": [{"name": "os", "value": "Ubuntu 18.04 x64"}] | |||
}, | |||
"credentials": { | |||
"credentials": { | |||
"params": [ | |||
{"name": "API-Key", "value": "{{VULTR_API_KEY}}"} | |||
] | |||
@@ -1 +1 @@ | |||
Subproject commit 23b2a5c0188618909feea52730e27e1c0685617e | |||
Subproject commit e0248d40efe8394a13da7c5ba0a422827e623611 |
@@ -70,6 +70,21 @@ | |||
} | |||
}, | |||
// Docker can be used for testing or for advanced use cases | |||
"DockerCompute": { | |||
"config": {}, | |||
"credentials": { | |||
// these are the default settings, change as needed | |||
"host": "unix:///var/run/docker.sock", | |||
"tlsVerify": "false", // if tlsVerify is "true" then certPath must be set | |||
"certPath": null, | |||
"registryUrl": null, | |||
"registryUsername": null, | |||
"registryEmail": null, | |||
"registryPassword": null | |||
} | |||
}, | |||
/////////////////////// | |||
// Storage | |||
/////////////////////// | |||