@@ -48,18 +48,15 @@ | |||||
src: supervisor_wg_monitor_connections.conf | src: supervisor_wg_monitor_connections.conf | ||||
dest: /etc/supervisor/conf.d/wg_monitor_connections.conf | dest: /etc/supervisor/conf.d/wg_monitor_connections.conf | ||||
# Don't setup algo when in restore mode, bubble_restore_monitor.sh will set it up after the CA key has been restored | |||||
- name: Run algo playbook to install algo | - name: Run algo playbook to install algo | ||||
shell: /root/ansible/roles/algo/algo/install_algo.sh | |||||
when: restore_key is not defined | |||||
block: | |||||
- name: Run install algo script including playbook | |||||
shell: /root/ansible/roles/algo/algo/install_algo.sh | |||||
# Don't start monitors when in restore mode, bubble_restore_monitor.sh will start it after algo is installed | |||||
- name: Run algo playbook to install algo | |||||
shell: bash -c "supervisorctl reload && sleep 5s && supervisorctl restart algo_refresh_users_monitor && supervisorctl restart wg_monitor_connections" | |||||
when: restore_key is not defined | |||||
- name: Restart algo related services | |||||
shell: bash -c "supervisorctl reload && sleep 5s && supervisorctl restart algo_refresh_users_monitor && supervisorctl restart wg_monitor_connections" | |||||
- name: Run algo playbook to install algo | |||||
shell: bash -c "supervisorctl reload && sleep 5s && supervisorctl stop algo_refresh_users_monitor && supervisorctl stop wg_monitor_connections" | |||||
when: restore_key is defined | |||||
- include: algo_firewall.yml | |||||
# Don't setup algo when in restore mode, bubble_restore_monitor.sh will set it up after the CA key has been restored | |||||
tags: algo_related | |||||
- include: algo_firewall.yml |
@@ -21,7 +21,7 @@ function die { | |||||
} | } | ||||
function log { | function log { | ||||
echo "${1}" >> ${LOG} | |||||
echo "$(date): ${1}" >> ${LOG} | |||||
} | } | ||||
START=$(date +%s) | START=$(date +%s) | ||||
@@ -92,7 +92,7 @@ cp ${RESTORE_BASE}/bubble.sql.gz ${BUBBLE_HOME}/sql/ \ | |||||
&& chgrp -R postgres ${BUBBLE_HOME}/sql \ | && chgrp -R postgres ${BUBBLE_HOME}/sql \ | ||||
&& chmod 550 ${BUBBLE_HOME}/sql \ | && chmod 550 ${BUBBLE_HOME}/sql \ | ||||
&& chmod 440 ${BUBBLE_HOME}/sql/* || die "Error restoring bubble database archive" | && chmod 440 ${BUBBLE_HOME}/sql/* || die "Error restoring bubble database archive" | ||||
su - postgres bash -c "cd ${BUBBLE_HOME}/sql && full_reset_db.sh drop" || die "Error restoring database" | |||||
su - postgres bash -c "cd ${BUBBLE_HOME}/sql && full_reset_db.sh drop restored_node" || die "Error restoring database" | |||||
# Remove old keys | # Remove old keys | ||||
log "Removing node keys" | log "Removing node keys" | ||||
@@ -107,21 +107,27 @@ log "Flushing redis" | |||||
echo "FLUSHALL" | redis-cli || die "Error flushing redis" | echo "FLUSHALL" | redis-cli || die "Error flushing redis" | ||||
# restore algo configs | # restore algo configs | ||||
log "Restoring algo configs" | |||||
CONFIGS_BACKUP=/home/bubble/.BUBBLE_ALGO_CONFIGS.tgz | CONFIGS_BACKUP=/home/bubble/.BUBBLE_ALGO_CONFIGS.tgz | ||||
if [[ ! -f ${CONFIGS_BACKUP} ]] ; then | if [[ ! -f ${CONFIGS_BACKUP} ]] ; then | ||||
log "Warning: Algo VPN configs backup not found: ${CONFIGS_BACKUP}, not installing algo" | log "Warning: Algo VPN configs backup not found: ${CONFIGS_BACKUP}, not installing algo" | ||||
else | else | ||||
ALGO_BASE=/root/ansible/roles/algo/algo | |||||
ANSIBLE_HOME="/root" | |||||
ANSIBLE_DIR="${ANSIBLE_HOME}/ansible" | |||||
ID_FILE="${ANSIBLE_HOME}/.ssh/bubble_rsa" | |||||
SSH_OPTIONS="--ssh-extra-args '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PreferredAuthentications=publickey -i ${ID_FILE}'" | |||||
ALGO_BASE=${ANSIBLE_DIR}/roles/algo/algo | |||||
if [[ ! -d ${ALGO_BASE} ]] ; then | if [[ ! -d ${ALGO_BASE} ]] ; then | ||||
die "Error restoring Algo VPN: directory ${ALGO_BASE} not found" | die "Error restoring Algo VPN: directory ${ALGO_BASE} not found" | ||||
fi | fi | ||||
cd ${ALGO_BASE} && tar xzf ${CONFIGS_BACKUP} || die "Error restoring algo VPN configs" | cd ${ALGO_BASE} && tar xzf ${CONFIGS_BACKUP} || die "Error restoring algo VPN configs" | ||||
# install/configure algo | |||||
${ALGO_BASE}/install_algo.sh || die "Error configuring or installing algo VPN" | |||||
# ensure user monitor is running | |||||
supervisorctl restart algo_refresh_users_monitor | |||||
cd "${ANSIBLE_DIR}" && \ | |||||
. ./venv/bin/activate && \ | |||||
bash -c \ | |||||
"ansible-playbook ${SSH_OPTIONS} --tags 'algo_related,always' --inventory ./hosts ./playbook.yml 2>&1 >> ${LOG}" \ | |||||
|| die "Error running ansible in post-restore. journalctl -xe = $(journalctl -xe | tail -n 50)" | |||||
fi | fi | ||||
# restart mitm proxy service | # restart mitm proxy service | ||||
@@ -135,7 +141,7 @@ supervisorctl restart bubble | |||||
# verify service is running OK | # verify service is running OK | ||||
log "Pausing for a bit, then verifying bubble server has successfully restarted after restore" | log "Pausing for a bit, then verifying bubble server has successfully restarted after restore" | ||||
sleep 60 | sleep 60 | ||||
curl https://$(hostname):${ADMIN_PORT}/api/.bubble || log "Error restarting bubble server" | |||||
curl https://$(hostname):${ADMIN_PORT}/api/.bubble || log "Error restarting bubble server - port ${ADMIN_PORT}" | |||||
# remove restore markers, we are done | # remove restore markers, we are done | ||||
log "Cleaning up temp files" | log "Cleaning up temp files" | ||||
@@ -5,6 +5,8 @@ function die { | |||||
exit 1 | exit 1 | ||||
} | } | ||||
INSTALL_MODE=${2:-{{install_type}}} | |||||
if [[ $(whoami) == "root" ]] ; then | if [[ $(whoami) == "root" ]] ; then | ||||
su - postgres ${0} ${@} | su - postgres ${0} ${@} | ||||
exit $? | exit $? | ||||
@@ -15,5 +17,5 @@ if [[ $(whoami) != "postgres" ]] ; then | |||||
fi | fi | ||||
cd ~bubble/sql \ | cd ~bubble/sql \ | ||||
&& init_bubble_db.sh {{ db_name }} {{ db_user }} {{ is_fork }} {{ install_type }} ${1} \ | |||||
&& init_bubble_db.sh {{ db_name }} {{ db_user }} {{ is_fork }} ${INSTALL_MODE} ${1} \ | |||||
|| die "error reinitializing database" | || die "error reinitializing database" |
@@ -88,13 +88,17 @@ | |||||
state: link | state: link | ||||
- name: Restart dnscrypt-proxy | - name: Restart dnscrypt-proxy | ||||
shell: service dnscrypt-proxy restart | |||||
service: | |||||
name: dnscrypt-proxy | |||||
state: restarted | |||||
tags: algo_related | |||||
- name: restart supervisord | - name: restart supervisord | ||||
service: | service: | ||||
name: supervisor | name: supervisor | ||||
enabled: yes | enabled: yes | ||||
state: restarted | state: restarted | ||||
tags: always | |||||
- import_tasks: route.yml | - import_tasks: route.yml | ||||
@@ -14,7 +14,7 @@ | |||||
value: 0 | value: 0 | ||||
sysctl_set: yes | sysctl_set: yes | ||||
- name: "Allow MITM private port" | |||||
- name: Allow MITM private port | |||||
iptables: | iptables: | ||||
chain: INPUT | chain: INPUT | ||||
action: insert | action: insert | ||||
@@ -26,33 +26,15 @@ | |||||
jump: ACCEPT | jump: ACCEPT | ||||
comment: Accept new local TCP DNS connections on private port | comment: Accept new local TCP DNS connections on private port | ||||
become: yes | become: yes | ||||
tags: algo_related | |||||
- name: Route port 80 through mitmproxy | |||||
iptables: | |||||
table: nat | |||||
chain: PREROUTING | |||||
action: insert | |||||
rule_num: 1 | |||||
protocol: tcp | |||||
destination_port: 80 | |||||
jump: REDIRECT | |||||
to_ports: "{{ mitm_port }}" | |||||
- name: Route port 443 through mitmproxy | |||||
iptables: | |||||
table: nat | |||||
chain: PREROUTING | |||||
action: insert | |||||
rule_num: 2 | |||||
protocol: tcp | |||||
destination_port: 443 | |||||
jump: REDIRECT | |||||
to_ports: "{{ mitm_port }}" | |||||
- name: Setup for MITM and save iptables | |||||
block: | |||||
- name: save iptables rules | |||||
shell: iptables-save > /etc/iptables/rules.v4 | |||||
become: yes | |||||
- name: save iptables rules | |||||
shell: iptables-save > /etc/iptables/rules.v4 | |||||
become: yes | |||||
- name: save iptables v6 rules | |||||
shell: ip6tables-save > /etc/iptables/rules.v6 | |||||
become: yes | |||||
- name: save iptables v6 rules | |||||
shell: ip6tables-save > /etc/iptables/rules.v6 | |||||
become: yes | |||||
tags: always |
@@ -120,7 +120,13 @@ public class VultrDriver extends ComputeServiceDriverBase { | |||||
// create server, check response | // create server, check response | ||||
final HttpResponseBean serverResponse = serverRequest.curl(); // fixme: we can do better than shelling to curl | final HttpResponseBean serverResponse = serverRequest.curl(); // fixme: we can do better than shelling to curl | ||||
if (serverResponse.getStatus() != 200) return die("start: error creating server: " + serverResponse); | if (serverResponse.getStatus() != 200) return die("start: error creating server: " + serverResponse); | ||||
final String subId = json(serverResponse.getEntityString(), JsonNode.class).get(VULTR_SUBID).textValue(); | |||||
final JsonNode responseJson; | |||||
try { | |||||
responseJson = json(serverResponse.getEntityString(), JsonNode.class); | |||||
} catch (IllegalStateException e) { | |||||
return die("start: error creating server (error parsing response as JSON): " + serverResponse); | |||||
} | |||||
final var subId = responseJson.get(VULTR_SUBID).textValue(); | |||||
node.setState(BubbleNodeState.booting); | node.setState(BubbleNodeState.booting); | ||||
node.setTag(TAG_INSTANCE_ID, subId); | node.setTag(TAG_INSTANCE_ID, subId); | ||||
@@ -20,7 +20,7 @@ import static org.cobbzilla.util.security.ShaUtil.sha256_hex; | |||||
public abstract class StorageServiceDriverBase<T> extends CloudServiceDriverBase<T> implements StorageServiceDriver { | public abstract class StorageServiceDriverBase<T> extends CloudServiceDriverBase<T> implements StorageServiceDriver { | ||||
private final Map<String, WriteRequest> requestMap = new ConcurrentHashMap<>(); | |||||
private static final Map<String, WriteRequest> requestMap = new ConcurrentHashMap<>(); | |||||
private static final Map<String, WriteRequestCleaner> cleaners = new ConcurrentHashMap<>(); | private static final Map<String, WriteRequestCleaner> cleaners = new ConcurrentHashMap<>(); | ||||
@@ -66,11 +66,11 @@ public abstract class StorageServiceDriverBase<T> extends CloudServiceDriverBase | |||||
} catch (IllegalStateException e) { | } catch (IllegalStateException e) { | ||||
if (e.getMessage().contains("timeout")) { | if (e.getMessage().contains("timeout")) { | ||||
if (countBytes == 0) { | if (countBytes == 0) { | ||||
return die("StorageServiceDriverBase._write: error: " + e); | |||||
return die("StorageServiceDriverBase._write: error (no bytes) ", e); | |||||
} | } | ||||
} | } | ||||
} catch (Exception e) { | } catch (Exception e) { | ||||
return die("StorageServiceDriverBase._write: error: " + e); | |||||
return die("StorageServiceDriverBase._write: exception ", e); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -13,6 +13,8 @@ import bubble.model.account.message.AccountAction; | |||||
import bubble.model.account.message.AccountMessageType; | import bubble.model.account.message.AccountMessageType; | ||||
import bubble.model.account.message.ActionTarget; | import bubble.model.account.message.ActionTarget; | ||||
import bubble.server.BubbleConfiguration; | import bubble.server.BubbleConfiguration; | ||||
import com.fasterxml.jackson.databind.JsonNode; | |||||
import lombok.NonNull; | |||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.glassfish.jersey.server.ContainerRequest; | import org.glassfish.jersey.server.ContainerRequest; | ||||
import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
@@ -20,9 +22,12 @@ import org.springframework.context.ApplicationContext; | |||||
import org.springframework.stereotype.Repository; | import org.springframework.stereotype.Repository; | ||||
import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||
import javax.annotation.Nullable; | |||||
import javax.validation.Valid; | |||||
import javax.ws.rs.*; | import javax.ws.rs.*; | ||||
import javax.ws.rs.core.Context; | import javax.ws.rs.core.Context; | ||||
import javax.ws.rs.core.Response; | import javax.ws.rs.core.Response; | ||||
import java.io.IOException; | |||||
import java.util.*; | import java.util.*; | ||||
import java.util.function.Predicate; | import java.util.function.Predicate; | ||||
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
@@ -32,6 +37,7 @@ import static bubble.cloud.auth.RenderedMessage.filteredInbox; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | import static org.cobbzilla.util.daemon.ZillaRuntime.die; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | ||||
import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON; | import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON; | ||||
import static org.cobbzilla.util.json.JsonUtil.*; | |||||
import static org.cobbzilla.util.reflect.ReflectionUtil.forName; | import static org.cobbzilla.util.reflect.ReflectionUtil.forName; | ||||
import static org.cobbzilla.util.reflect.ReflectionUtil.instantiate; | import static org.cobbzilla.util.reflect.ReflectionUtil.instantiate; | ||||
import static org.cobbzilla.wizard.resources.ResourceUtil.*; | import static org.cobbzilla.wizard.resources.ResourceUtil.*; | ||||
@@ -117,4 +123,16 @@ public class DebugResource { | |||||
} | } | ||||
} | } | ||||
@POST @Path("/echo") | |||||
public Response echoJsonInLog(@Context ContainerRequest ctx, | |||||
@Valid @NonNull final JsonNode input, | |||||
@QueryParam("respondWith") @Nullable final String respondWith) throws IOException { | |||||
final var output = "ECHO: \n" + toJsonOrDie(input); | |||||
log.info(output); | |||||
if (empty(respondWith)) return ok(); | |||||
log.debug("Responding with value in path: " + respondWith); | |||||
return ok(getNodeAsJava(findNode(input, respondWith), "")); | |||||
} | |||||
} | } |
@@ -146,7 +146,7 @@ public class InboundNotifyResource { | |||||
? stream(APPLICATION_OCTET_STREAM, data) | ? stream(APPLICATION_OCTET_STREAM, data) | ||||
: notFound(storageRequest.getKey()); | : notFound(storageRequest.getKey()); | ||||
} catch (Exception e) { | } catch (Exception e) { | ||||
return die("readStorage: "+e); | |||||
return die("readStorage: exception", e); | |||||
} finally { | } finally { | ||||
storageStreamService.clearToken(token); | storageStreamService.clearToken(token); | ||||
} | } | ||||
@@ -49,9 +49,14 @@ sudo pip3 install setuptools psycopg2-binary || die "Error pip3 installing setup | |||||
SSH_OPTIONS="--ssh-extra-args '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PreferredAuthentications=publickey -i ${ID_FILE}'" | SSH_OPTIONS="--ssh-extra-args '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PreferredAuthentications=publickey -i ${ID_FILE}'" | ||||
SKIP_TAGS="" | |||||
if [[ -n "{{restoreKey}}" ]] ; then | |||||
SKIP_TAGS="--skip-tags algo_related" | |||||
fi | |||||
cd "${ANSIBLE_DIR}" && \ | cd "${ANSIBLE_DIR}" && \ | ||||
virtualenv -p python3 ./venv && \ | virtualenv -p python3 ./venv && \ | ||||
. ./venv/bin/activate && \ | . ./venv/bin/activate && \ | ||||
pip3 install ansible && \ | pip3 install ansible && \ | ||||
bash -c "ansible-playbook ${SSH_OPTIONS} --inventory ./hosts ./playbook.yml" \ | |||||
bash -c "ansible-playbook ${SSH_OPTIONS} ${SKIP_TAGS} --inventory ./hosts ./playbook.yml" \ | |||||
|| die "Error running ansible. journalctl -xe = $(journalctl -xe | tail -n 50)" | || die "Error running ansible. journalctl -xe = $(journalctl -xe | tail -n 50)" |
@@ -13,7 +13,7 @@ | |||||
{ "percent": 44,"messageKey":"role_bubble_jar", "pattern":"TASK \\[bubble : Install bubble jar] \\*{5,}" }, | { "percent": 44,"messageKey":"role_bubble_jar", "pattern":"TASK \\[bubble : Install bubble jar] \\*{5,}" }, | ||||
{ "percent": 48,"messageKey":"role_bubble_db", "pattern":"TASK \\[bubble : Populate database] \\*{5,}" }, | { "percent": 48,"messageKey":"role_bubble_db", "pattern":"TASK \\[bubble : Populate database] \\*{5,}" }, | ||||
{ "percent": 51,"messageKey":"role_bubble_restore", "pattern":"TASK \\[bubble : Install restore helper scripts] \\*{5,}" }, | { "percent": 51,"messageKey":"role_bubble_restore", "pattern":"TASK \\[bubble : Install restore helper scripts] \\*{5,}" }, | ||||
{ "percent": 52,"messageKey":"role_bubble_algo", "pattern":"TASK \\[algo : Run algo playbook to install algo] \\*{5,}" }, | |||||
{ "percent": 52,"messageKey":"role_bubble_algo", "pattern":"TASK \\[algo : [\\w\\s]+] \\*{5,}" }, | |||||
{ "percent": 76,"messageKey":"role_nginx", "pattern":"TASK \\[nginx : [\\w\\s]+] \\*{5,}" }, | { "percent": 76,"messageKey":"role_nginx", "pattern":"TASK \\[nginx : [\\w\\s]+] \\*{5,}" }, | ||||
{ "percent": 81,"messageKey":"role_nginx_certbot", "pattern":"TASK \\[nginx : Init certbot] \\*{5,}" }, | { "percent": 81,"messageKey":"role_nginx_certbot", "pattern":"TASK \\[nginx : Init certbot] \\*{5,}" }, | ||||
{ "percent": 91,"messageKey":"role_mitmproxy", "pattern":"TASK \\[mitmproxy : [\\w\\s]+] \\*{5,}" }, | { "percent": 91,"messageKey":"role_mitmproxy", "pattern":"TASK \\[mitmproxy : [\\w\\s]+] \\*{5,}" }, | ||||
@@ -0,0 +1,14 @@ | |||||
/** | |||||
* Copyright (c) 2020 Bubble, Inc. All rights reserved. | |||||
* For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ | |||||
*/ | |||||
package bubble.test; | |||||
import org.junit.Test; | |||||
public class DebugCallsTest extends ActivatedBubbleModelTestBase { | |||||
@Override protected String getManifest() { return "manifest-empty"; } | |||||
@Test public void testEcho() throws Exception { modelTest("debug_echo"); } | |||||
} |
@@ -16,15 +16,12 @@ | |||||
}, | }, | ||||
{ | { | ||||
"before": "sleep 3s", | |||||
"comment": "check email inbox, expect network keys request", | |||||
"request": { | |||||
"uri": "debug/inbox/email/<<rootEmail>>?type=request&action=password&target=network" | |||||
}, | |||||
"comment": "await and get network keys request in email inbox", | |||||
"before": "await_url debug/inbox/email/<<rootEmail>>?type=request&action=password&target=network 3m 5s len(await_json) > 0", | |||||
"request": { "uri": "debug/inbox/email/<<rootEmail>>?type=request&action=password&target=network" }, | |||||
"response": { | "response": { | ||||
"store": "emailInbox", | "store": "emailInbox", | ||||
"check": [ | "check": [ | ||||
{"condition": "json.length >= 1"}, | |||||
{"condition": "'{{json.[0].ctx.message.messageType}}' == 'request'"}, | {"condition": "'{{json.[0].ctx.message.messageType}}' == 'request'"}, | ||||
{"condition": "'{{json.[0].ctx.message.action}}' == 'password'"}, | {"condition": "'{{json.[0].ctx.message.action}}' == 'password'"}, | ||||
{"condition": "'{{json.[0].ctx.message.target}}' == 'network'"} | {"condition": "'{{json.[0].ctx.message.target}}' == 'network'"} | ||||
@@ -41,15 +38,12 @@ | |||||
}, | }, | ||||
{ | { | ||||
"before": "sleep 3s", | |||||
"comment": "check email inbox for key request confirmation", | |||||
"request": { | |||||
"uri": "debug/inbox/email/<<rootEmail>>?type=confirmation&action=password&target=network" | |||||
}, | |||||
"comment": "await and get key request confirmation from email inbox", | |||||
"before": "await_url debug/inbox/email/<<rootEmail>>?type=confirmation&action=password&target=network 3m 5s len(await_json) > 0", | |||||
"request": { "uri": "debug/inbox/email/<<rootEmail>>?type=confirmation&action=password&target=network" }, | |||||
"response": { | "response": { | ||||
"store": "emailInbox", | "store": "emailInbox", | ||||
"check": [ | "check": [ | ||||
{"condition": "json.length >= 1"}, | |||||
{"condition": "'{{json.[0].ctx.message.messageType}}' == 'confirmation'"}, | {"condition": "'{{json.[0].ctx.message.messageType}}' == 'confirmation'"}, | ||||
{"condition": "'{{json.[0].ctx.message.action}}' == 'password'"}, | {"condition": "'{{json.[0].ctx.message.action}}' == 'password'"}, | ||||
{"condition": "'{{json.[0].ctx.message.target}}' == 'network'"} | {"condition": "'{{json.[0].ctx.message.target}}' == 'network'"} | ||||
@@ -111,13 +111,13 @@ | |||||
{ | { | ||||
"comment": "list all payment methods", | "comment": "list all payment methods", | ||||
"request": { "uri": "me/paymentMethods?all=true" }, | |||||
"request": { "uri": "me/paymentMethods" }, | |||||
"response": { "store": "paymentMethods" } | "response": { "store": "paymentMethods" } | ||||
}, | }, | ||||
{ | { | ||||
"comment": "add payment method for the user", | "comment": "add payment method for the user", | ||||
"onlyIf": "len(paymentMethods) == 0", | |||||
"onlyIf": "!match_any(paymentMethods, function(m) { return !m.hasPromotion() && m.getPaymentMethodType() != `promotional_credit`; })", | |||||
"before": "stripe_tokenize_card", | "before": "stripe_tokenize_card", | ||||
"request": { | "request": { | ||||
"uri": "me/paymentMethods", | "uri": "me/paymentMethods", | ||||
@@ -127,15 +127,15 @@ | |||||
}, | }, | ||||
{ | { | ||||
"comment": "list all payment methods again after creating one", | |||||
"onlyIf": "len(paymentMethods) == 0", | |||||
"request": { "uri": "me/paymentMethods?all=true" }, | |||||
"response": { "store": "paymentMethods", "check": [{ "condition": "len(json) == 1" }] } | |||||
"comment": "wait for the one created above and fetch all payment methods again including that one", | |||||
"onlyIf": "!match_any(paymentMethods, function(m) { return !m.hasPromotion() && m.getPaymentMethodType() != `promotional_credit`; })", | |||||
"before": "await_url me/paymentMethods 5m 10s match_any(await_json, function(m) { return !m.hasPromotion() && m.getPaymentMethodType() != `promotional_credit`; })", | |||||
"request": { "uri": "me/paymentMethods" }, | |||||
"response": { "store": "paymentMethods" } | |||||
}, | }, | ||||
{ | { | ||||
"comment": "add plan, using the first found payment method for the new bubble", | "comment": "add plan, using the first found payment method for the new bubble", | ||||
"before": "sleep 24s", | |||||
"request": { | "request": { | ||||
"uri": "me/plans", | "uri": "me/plans", | ||||
"method": "put", | "method": "put", | ||||
@@ -147,7 +147,9 @@ | |||||
"plan": "<<plan>>", | "plan": "<<plan>>", | ||||
"footprint": "<<footprint>>", | "footprint": "<<footprint>>", | ||||
"sendMetrics": <<sendMetrics>>, | "sendMetrics": <<sendMetrics>>, | ||||
"paymentMethodObject": { "uuid": "{{ paymentMethods.[0].uuid }}" } | |||||
"paymentMethodObject": { | |||||
"uuid": "{{ js '_find(paymentMethods, function(m) { return !m.hasPromotion() && m.getPaymentMethodType() != `promotional_credit`; }).getUuid()' }}" | |||||
} | |||||
} | } | ||||
}, | }, | ||||
"response": { "store": "plan" } | "response": { "store": "plan" } | ||||
@@ -166,7 +168,7 @@ | |||||
{ | { | ||||
"comment": "call API of deployed node after some grace period, ensure it is running", | "comment": "call API of deployed node after some grace period, ensure it is running", | ||||
"before": "await_url .bubble 20m:40m 20s", | |||||
"before": "await_url .bubble 20m:20m 20s", | |||||
"connection": { | "connection": { | ||||
"name": "<<bubbleConnectionVar>>", | "name": "<<bubbleConnectionVar>>", | ||||
"baseUri": "https://{{<<networkVar>>.host}}.<<network>>.<<domain>>:{{serverConfig.nginxPort}}/api" | "baseUri": "https://{{<<networkVar>>.host}}.<<network>>.<<domain>>:{{serverConfig.nginxPort}}/api" | ||||
@@ -218,4 +220,4 @@ | |||||
] | ] | ||||
} | } | ||||
} | } | ||||
] | |||||
] |
@@ -0,0 +1,48 @@ | |||||
[ | |||||
{ | |||||
"comment": "Simplest example of using ECHO debug call without the query param", | |||||
"request": { "uri": "debug/echo", "entity": { "anything": "something" } }, | |||||
"response": { "check": [{ "condition": "len(json) == 0" }] }, | |||||
"after": "add_to_ctx { \"added\": \"val\", \"addedInner\": { \"inner\": \"value\", \"another\": \"variable\" }, \"addedArray\": [ \"abc\", \"def\" ] }" | |||||
}, | |||||
{ | |||||
"comment": "Example of using ECHO debug call and echo_in_log after", | |||||
"request": { | |||||
"uri": "debug/echo?respondWith=inner.comment", | |||||
"entity": { | |||||
"inner": { "comment": "Fixed text comment" }, | |||||
"non-existing": "{{notexistingvar}}" | |||||
} | |||||
}, | |||||
"response": { | |||||
"raw": true, | |||||
"store": "echoedResponse", | |||||
"check": [{ "condition": "response.json == 'Fixed text comment'" }] | |||||
}, | |||||
"after": "echo_in_log Test:\n\tnon existent value: {{somethingThatDoesntExist}}\n\tjust stored response: {{echoedResponse}}" | |||||
}, | |||||
{ | |||||
"comment": "Another example of using ECHO debug call and echo_in_log after", | |||||
"before": "add_to_ctx { \"brand\": \"new\" }", | |||||
"request": { | |||||
"uri": "debug/echo?respondWith=inner", | |||||
"entity": { | |||||
"previouslyStored": "{{echoedResponse}}", | |||||
"inner": { "comment": "Another fixed text comment" } | |||||
} | |||||
}, | |||||
"response": { | |||||
"store": "echoedJson", | |||||
"check": [ | |||||
{ "condition": "json.get('comment') == 'Another fixed text comment'" }, | |||||
{ "condition": "'{{brand}}' == 'new'" }, | |||||
{ "condition": "'{{addedInner.another}}' == 'variable'" }, | |||||
{ "condition": "'{{added}}' == 'val'" } | |||||
] | |||||
}, | |||||
"after": "echo_in_log \"And now the stored value is: {{echoedJson.comment}}\"" | |||||
// echo_in_log is, of course, available within `before` also | |||||
} | |||||
] |
@@ -1,6 +1,7 @@ | |||||
[ | [ | ||||
{ | { | ||||
"comment": "login as root on sage node", | |||||
"comment": "login as root on sage node (adding username to ctx also)", | |||||
"before": "add_to_ctx { \"username\": \"bubble-user\" }", | |||||
"connection": { | "connection": { | ||||
"name": "sageConnection", | "name": "sageConnection", | ||||
"baseUri": "https://{{sageFqdn}}:{{serverConfig.nginxPort}}/api" | "baseUri": "https://{{sageFqdn}}:{{serverConfig.nginxPort}}/api" | ||||
@@ -41,11 +42,11 @@ | |||||
"params": { | "params": { | ||||
"sageFqdn": "{{sageFqdn}}", | "sageFqdn": "{{sageFqdn}}", | ||||
"rootPassword": "{{sageRootPass}}", | "rootPassword": "{{sageRootPass}}", | ||||
"username": "bubble-user", | |||||
"username": "{{username}}", | |||||
"password": "password1!", | "password": "password1!", | ||||
"userSessionVar": "userSession", | "userSessionVar": "userSession", | ||||
"network": "bubble-{{rand 5}}", | "network": "bubble-{{rand 5}}", | ||||
"email": "bubble-user@example.com", | |||||
"email": "{{username}}@example.com", | |||||
"plan": "bubble", | "plan": "bubble", | ||||
"networkVar": "bubbleNetwork", | "networkVar": "bubbleNetwork", | ||||
"bubbleConnectionVar": "bubbleConnection", | "bubbleConnectionVar": "bubbleConnection", | ||||
@@ -61,9 +62,6 @@ | |||||
"uri": "me/networks/{{bubbleNetwork.network}}/storage/write/test_file_{{bubbleNetwork.network}}.txt", | "uri": "me/networks/{{bubbleNetwork.network}}/storage/write/test_file_{{bubbleNetwork.network}}.txt", | ||||
"headers": { "Content-Type": "multipart/form-data" }, | "headers": { "Content-Type": "multipart/form-data" }, | ||||
"entity": {"file": "data:this is a test file: {{rand 20}}"} | "entity": {"file": "data:this is a test file: {{rand 20}}"} | ||||
}, | |||||
"response": { | |||||
"store": "fileMeta" | |||||
} | } | ||||
}, | }, | ||||
@@ -71,11 +69,11 @@ | |||||
"comment": "add verified email to root account on new node", | "comment": "add verified email to root account on new node", | ||||
"include": "add_approved_contact", | "include": "add_approved_contact", | ||||
"params": { | "params": { | ||||
"username": "bubble-user", | |||||
"username": "{{username}}", | |||||
"userSession": "bubbleUserSession", | "userSession": "bubbleUserSession", | ||||
"userConnection": "bubbleConnection", | "userConnection": "bubbleConnection", | ||||
"contactInfo": "bubble-user@example.com", | |||||
"contactLookup": "bubble-user@example.com", | |||||
"contactInfo": "{{username}}@example.com", | |||||
"contactLookup": "{{username}}@example.com", | |||||
"authFactor": "not_required", | "authFactor": "not_required", | ||||
"rootSession": "bubbleUserSession", | "rootSession": "bubbleUserSession", | ||||
"rootConnection": "bubbleConnection" | "rootConnection": "bubbleConnection" | ||||
@@ -94,9 +92,6 @@ | |||||
"agreeToTerms": true, | "agreeToTerms": true, | ||||
"contact": {"type": "email", "info": "user-{{rand 5}}@example.com"} | "contact": {"type": "email", "info": "user-{{rand 5}}@example.com"} | ||||
} | } | ||||
}, | |||||
"response": { | |||||
"store": "newUser" | |||||
} | } | ||||
}, | }, | ||||
@@ -108,28 +103,14 @@ | |||||
}, | }, | ||||
{ | { | ||||
"comment": "backup network", | |||||
"request": { | |||||
"uri": "me/networks/{{bubbleNetwork.network}}/backups/test_backup", | |||||
"method": "put" | |||||
}, | |||||
"response": { | |||||
"store": "backup" | |||||
} | |||||
"comment": "backup network for later restore", | |||||
"request": { "method": "put", "uri": "me/networks/{{bubbleNetwork.network}}/backups/test_backup" } | |||||
}, | }, | ||||
{ | { | ||||
"before": "await_url me/networks/{{bubbleNetwork.network}}/backups/test_backup?status=backup_completed 5m 10s", | |||||
"comment": "find completed backup", | |||||
"request": { | |||||
"uri": "me/networks/{{bubbleNetwork.network}}/backups/test_backup" | |||||
}, | |||||
"response": { | |||||
"store": "backup", | |||||
"check": [ | |||||
{"condition": "json.getStatus().name() == 'backup_completed'"} | |||||
] | |||||
} | |||||
"comment": "await completed backup and store in the context", | |||||
"before": "await_url me/networks/{{bubbleNetwork.network}}/backups/test_backup?status=backup_completed 90s:10m 15s", | |||||
"request": { "uri": "me/networks/{{bubbleNetwork.network}}/backups/test_backup?status=backup_completed" } | |||||
}, | }, | ||||
{ | { | ||||
@@ -137,7 +118,7 @@ | |||||
"include": "get_network_keys", | "include": "get_network_keys", | ||||
"params": { | "params": { | ||||
"network": "{{bubbleNetwork.network}}", | "network": "{{bubbleNetwork.network}}", | ||||
"rootEmail": "bubble-user@example.com", | |||||
"rootEmail": "{{username}}@example.com", | |||||
"networkKeysVar": "networkKeys", | "networkKeysVar": "networkKeys", | ||||
"networkKeysPassword": "Passw0rd!!" | "networkKeysPassword": "Passw0rd!!" | ||||
} | } | ||||
@@ -155,13 +136,9 @@ | |||||
}, | }, | ||||
{ | { | ||||
"comment": "verify network is stopped", | |||||
"request": {"uri": "me/networks/{{bubbleNetwork.network}}" }, | |||||
"response": { | |||||
"check": [ | |||||
{"condition": "json.getState().name() == 'stopped'"} | |||||
] | |||||
} | |||||
"comment": "wait for network to stop", | |||||
"before": "await_url me/networks/{{bubbleNetwork.network}} 5m 10s await_json.getState().name() == 'stopped'", | |||||
"request": { "uri": "me" } | |||||
}, | }, | ||||
{ | { | ||||
@@ -179,12 +156,12 @@ | |||||
}, | }, | ||||
{ | { | ||||
"before": "await_url .bubble 40m 20s", | |||||
"comment": "restore node using restoreKey", | |||||
"before": "await_url .bubble 16m:20m 20s", | |||||
"connection": { | "connection": { | ||||
"name": "restoredBubbleConnection", | "name": "restoredBubbleConnection", | ||||
"baseUri": "https://{{restoreNN.fqdn}}:{{serverConfig.nginxPort}}/api" | "baseUri": "https://{{restoreNN.fqdn}}:{{serverConfig.nginxPort}}/api" | ||||
}, | }, | ||||
"comment": "restore node using restoreKey", | |||||
"request": { | "request": { | ||||
"uri": "auth/restore/{{restoreNN.restoreKey}}", | "uri": "auth/restore/{{restoreNN.restoreKey}}", | ||||
"entity": { | "entity": { | ||||
@@ -193,17 +170,16 @@ | |||||
}, | }, | ||||
"method": "put" | "method": "put" | ||||
}, | }, | ||||
"after": "sleep 240s" // give the restore some time to stop the server, restore and restart | |||||
"after": "await_url .bubble 9m:10m 20s" // give the restore some time to stop the server, restore and restart | |||||
}, | }, | ||||
{ | { | ||||
"before": "await_url .bubble 10m 20s", | |||||
"comment": "login to restored bubble", | "comment": "login to restored bubble", | ||||
"request": { | "request": { | ||||
"session": "new", | "session": "new", | ||||
"uri": "auth/login", | "uri": "auth/login", | ||||
"entity": { | "entity": { | ||||
"name": "bubble-user", | |||||
"name": "{{username}}", | |||||
"password": "password1!" | "password": "password1!" | ||||
} | } | ||||
}, | }, | ||||
@@ -240,15 +216,16 @@ | |||||
"request": { | "request": { | ||||
"uri": "me/networks/{{restoreNN.network}}/actions/stop", | "uri": "me/networks/{{restoreNN.network}}/actions/stop", | ||||
"method": "post" | "method": "post" | ||||
} | |||||
}, | |||||
"after": "await_url me/networks/{{restoreNN.network}} 5m 10s await_json.getState().name() == 'stopped'" | |||||
}, | }, | ||||
{ | { | ||||
"comment": "delete restored bubble network from sage", | |||||
"comment": "delete restored bubble network from sage by deleting plan which should have the same name", | |||||
"request": { | "request": { | ||||
"uri": "me/networks/{{restoreNN.network}}", | |||||
"uri": "me/plans/{{restoreNN.network}}", | |||||
"method": "delete" | "method": "delete" | ||||
}, | }, | ||||
"after": "verify_unreachable https://{{restoreNN.fqdn}}:{{serverConfig.nginxPort}}/api/me" | "after": "verify_unreachable https://{{restoreNN.fqdn}}:{{serverConfig.nginxPort}}/api/me" | ||||
} | } | ||||
] | |||||
] |