feat: reduce build time for fxa-email-service

This commit is contained in:
Ben Bangert 2019-07-19 10:46:23 -07:00
Родитель 467688420b
Коммит 45b304acb3
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 340D6D716D25CCA6
10 изменённых файлов: 316 добавлений и 193 удалений

Просмотреть файл

@ -116,14 +116,13 @@ jobs:
- run: ../../.circleci/deploy.sh fxa-oauth-server
install-content-server:
resource_class: xlarge
executor: nodejs
working_directory: ~/project/packages/fxa-content-server
steps:
- attach_workspace:
at: ~/.
- setup_remote_docker
- run: ../../.circleci/install-content-server.sh
- persist_to_workspace:
@ -152,6 +151,9 @@ jobs:
- setup_remote_docker
- deploy:
command: ../../_scripts/install_docker.sh
- deploy:
command: ../../.circleci/build.sh fxa-content-server
- deploy:
@ -237,12 +239,41 @@ jobs:
# --ignore ref: https://github.com/crossbeam-rs/crossbeam/issues/401
# hyper/reqwest/tokio -> tokio-threadpool -> crossbeam-deque -> crossbeam-epoch -> memoffset
cargo audit --ignore RUSTSEC-2019-0011
mkdir -m 755 bin
mkdir -m 755 bin/config
cargo build --release
cp config/* bin/config
cp target/release/fxa_email_send bin
cp target/release/fxa_email_queues bin
cargo clean
- store_artifacts:
path: fxa-auth-db-mysql.log
- setup_remote_docker
- run: |
../../.circleci/build.sh fxa-email-service
../../.circleci/deploy.sh fxa-email-service
../../.circleci/tag.sh fxa-email-service
fxa-email-service-tag:
resource_class: xlarge
docker:
- image: circleci/node:10
working_directory: ~/project/packages/fxa-email-service
steps:
- attach_workspace:
at: ~/
- run: |
. ../../.circleci/install-rust.sh
cargo --version
rustc --version
mkdir -m 755 bin
mkdir -m 755 bin/config
cargo build --release
cp config/* bin/config
cp target/release/fxa_email_send bin
cp target/release/fxa_email_queues bin
cargo clean
- setup_remote_docker
- run: |
../../.circleci/tag.sh fxa-email-service
docs:
docker:
@ -419,16 +450,6 @@ workflows:
module: fxa-customs-server
requires:
- install
- deploy-module:
filters:
tags:
only: /.*/
branches:
ignore: /.*/
name: fxa-email-service
module: fxa-email-service
requires:
- install
- deploy-module:
filters:
tags:
@ -505,3 +526,11 @@ workflows:
ignore: /.*/
requires:
- install
- fxa-email-service-tag:
filters:
tags:
only: /.*/
branches:
ignore: /.*/
requires:
- install

Просмотреть файл

@ -8,8 +8,7 @@ if grep -e "fxa-content-server" -e 'all' $DIR/../packages/test.list; then
cp ../version.json ./
cp ../version.json config
cd $DIR/..
CIRCLECI=false npm install
npx pm2 kill
CIRCLECI=false SKIP_DOCKER=true npm install
else
exit 0;
fi

55
.circleci/tag.sh Executable file
Просмотреть файл

@ -0,0 +1,55 @@
#!/bin/bash -e
MODULE=$1
DIR=$(dirname "$0")
if grep -e "$MODULE" -e 'all' $DIR/../packages/test.list; then
docker -v
# Place version.json so it is available as `/app/version.json` in the
# container, and also as `/app/config/version.json`, creating /app/config
# if needed.
cp $DIR/../packages/version.json .
mkdir -p config
cp $DIR/../packages/version.json config
if [[ -e Dockerfile-tag ]]; then
docker build -f Dockerfile-tag -t ${MODULE}:build .
# docker run --rm -it ${MODULE}:build npm ls --production
else
exit 0;
fi
if [ "${CIRCLE_BRANCH}" == "master" ]; then
DOCKER_TAG="latest"
fi
if [[ "${CIRCLE_BRANCH}" == feature* ]] || [[ "${CIRCLE_BRANCH}" == dockerpush* ]]; then
DOCKER_TAG="${CIRCLE_BRANCH}"
fi
if [ -n "${CIRCLE_TAG}" ]; then
DOCKER_TAG="$CIRCLE_TAG"
fi
if [ "$MODULE_SUFFIX" = "" ]; then
MODULE_QUALIFIED="$MODULE"
else
MODULE_QUALIFIED="${MODULE}-${MODULE_SUFFIX}"
fi
REPO=$(echo ${MODULE} | sed 's/-/_/g')
DOCKER_USER=DOCKER_USER_${REPO}
DOCKER_PASS=DOCKER_PASS_${REPO}
DOCKERHUB_REPO=mozilla/${MODULE_QUALIFIED}
if [ -n "${DOCKER_TAG}" ] && [ -n "${!DOCKER_PASS}" ] && [ -n "${!DOCKER_USER}" ]; then
echo "${!DOCKER_PASS}" | docker login -u "${!DOCKER_USER}" --password-stdin
echo ${DOCKERHUB_REPO}:${DOCKER_TAG}
docker tag ${MODULE}:build ${DOCKERHUB_REPO}:${DOCKER_TAG}
docker images
docker push ${DOCKERHUB_REPO}:${DOCKER_TAG}
fi
else
exit 0;
fi

Просмотреть файл

@ -1,80 +1,3 @@
#!/bin/sh -ex
_scripts/check.sh
# Set ulimit, need it for npm
ulimit -S -n 2048 || echo "Setting ulimit failed"
# Install and Setup all the projects
cd packages
cd fxa-content-server; npm ci; cp server/config/local.json-dist server/config/local.json; cd ..
cd fxa-auth-server
npm ci
node ./scripts/gen_keys.js
node ./scripts/gen_vapid_keys.js
node ./fxa-oauth-server/scripts/gen_keys
../../_scripts/clone-authdb.sh
cd ..
cd fxa-auth-db-mysql; npm ci; cd ..
# cd fxa-auth-server; npm link ../fxa-auth-db-mysql; cd ..
PATH=$PATH:$HOME/.cargo/bin
cd fxa-email-service;
cargo build --bin fxa_email_send;
../../_scripts/clone-authdb.sh
cd ..
cd browserid-verifier; npm ci; cd ..
cd fxa-js-client; npm ci; npx grunt sjcl; cd ..
cd fxa-customs-server; npm ci; cd ..
cd fxa-event-broker; npm ci; cd ..
cd fxa-oauth-console; npm ci; cd ..
cd fxa-payments-server; npm ci; cd ..
# cd fxa-auth-server/fxa-oauth-server; npm ci; cd ../..
cd fxa-profile-server; npm ci; mkdir -p var/public/; cd ..
cd fxa-basket-proxy; npm ci; cd ..
# 123done does not have an npm-shrinkwrap.json file and cannot use `npm ci`
cd 123done; npm i; cd ..
cd fxa-shared; npm ci; cd ..
cd fxa-geodb; npm i; cd ..
cd fxa-email-event-proxy; npm ci; cd ..
cd fxa-support-panel ; npm ci; cd ..
cd ..
docker network create fxa-net || true
docker pull memcached
docker pull mozilla/syncserver
docker pull mozilla/pushbox
docker pull pafortin/goaws
docker pull redis
docker pull mysql/mysql-server:5.6
docker pull jdlk7/firestore-emulator
docker pull knarz/pubsub-emulator
ln -sf node_modules/.bin/pm2 pm2
_scripts/install_packages.sh
_scripts/install_docker.sh

18
_scripts/install_docker.sh Executable file
Просмотреть файл

@ -0,0 +1,18 @@
#!/bin/sh -ex
docker network create fxa-net || true
docker pull memcached
docker pull mozilla/syncserver
docker pull mozilla/pushbox
docker pull pafortin/goaws
docker pull redis
docker pull mysql/mysql-server:5.6
docker pull jdlk7/firestore-emulator
docker pull knarz/pubsub-emulator

34
_scripts/install_packages.sh Executable file
Просмотреть файл

@ -0,0 +1,34 @@
#!/bin/sh -ex
_scripts/check.sh
# Set ulimit, need it for npm
ulimit -S -n 2048 || echo "Setting ulimit failed"
# Install and Setup all the projects
cd packages
PATH=$PATH:$HOME/.cargo/bin
# Now for concurrently!
../node_modules/.bin/concurrently \
"cd fxa-content-server; npm ci; cp server/config/local.json-dist server/config/local.json" \
"cd fxa-auth-server; npm ci; node ./scripts/gen_keys.js; node ./scripts/gen_vapid_keys.js; node ./fxa-oauth-server/scripts/gen_keys; ../../_scripts/clone-authdb.sh" \
"cd fxa-auth-db-mysql; npm ci" \
"cd fxa-email-service; cargo build --bin fxa_email_send; ../../_scripts/clone-authdb.sh" \
"cd browserid-verifier; npm ci" \
"cd fxa-js-client; npm ci; npx grunt sjcl" \
"cd fxa-customs-server; npm ci" \
"cd fxa-event-broker; npm ci" \
"cd fxa-oauth-console; npm ci" \
"cd fxa-payments-server; npm ci" \
"cd fxa-profile-server; npm ci; mkdir -p var/public/" \
"cd fxa-basket-proxy; npm ci" \
"cd 123done; npm i" \
"cd fxa-shared; npm ci" \
"cd fxa-geodb; npm i" \
"cd fxa-email-event-proxy; npm i" \
"cd fxa-support-panel ; npm ci"
cd ..
ln -sf node_modules/.bin/pm2 pm2

Просмотреть файл

@ -3,7 +3,11 @@
if [ "${CIRCLECI}" = "true" ]; then
exit 0
else
if [ "${SKIP_DOCKER}" = "true" ]; then
_scripts/install_packages.sh
exit 0
fi
_scripts/install_all.sh
pm2 delete servers.json && pm2 start servers.json
echo "Use './pm2 kill' to stop all the servers"
fi
fi

231
package-lock.json сгенерированный
Просмотреть файл

@ -2290,6 +2290,91 @@
}
}
},
"concurrently": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-4.1.1.tgz",
"integrity": "sha512-48+FE5RJ0qc8azwKv4keVQWlni1hZeSjcWr8shBelOBtBHcKj1aJFM9lHRiSc1x7lq416pkvsqfBMhSRja+Lhw==",
"requires": {
"chalk": "^2.4.1",
"date-fns": "^1.23.0",
"lodash": "^4.17.10",
"read-pkg": "^4.0.1",
"rxjs": "^6.3.3",
"spawn-command": "^0.0.2-1",
"supports-color": "^4.5.0",
"tree-kill": "^1.1.0",
"yargs": "^12.0.1"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"requires": {
"color-convert": "^1.9.0"
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
"requires": {
"error-ex": "^1.3.1",
"json-parse-better-errors": "^1.0.1"
}
},
"pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
},
"read-pkg": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz",
"integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=",
"requires": {
"normalize-package-data": "^2.3.2",
"parse-json": "^4.0.0",
"pify": "^3.0.0"
}
},
"supports-color": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
"integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
"requires": {
"has-flag": "^2.0.0"
},
"dependencies": {
"has-flag": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
"integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE="
}
}
}
}
},
"config-chain": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz",
@ -2975,15 +3060,6 @@
"which": "^1.2.9"
}
},
"cross-spawn-async": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/cross-spawn-async/-/cross-spawn-async-2.2.5.tgz",
"integrity": "sha1-hF/wwINKPe2dFg2sptOQkGuyiMw=",
"requires": {
"lru-cache": "^4.0.0",
"which": "^1.2.8"
}
},
"culvert": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/culvert/-/culvert-0.1.2.tgz",
@ -3699,32 +3775,6 @@
"pinkie-promise": "^2.0.0"
}
},
"firefox-launch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/firefox-launch/-/firefox-launch-1.1.0.tgz",
"integrity": "sha1-c24XocOtsvDSeBdVpKDqSHBUnJU=",
"requires": {
"firefox-location": "^1.0.0",
"mkdirp": "^0.5.0",
"quick-tmp": "0.0.0",
"rimraf": "^2.2.8",
"shallow-copy": "0.0.1"
}
},
"firefox-location": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/firefox-location/-/firefox-location-1.0.2.tgz",
"integrity": "sha1-S+5a+TewR5Qf8glkTK/479MrK0c=",
"requires": {
"userhome": "^1.0.0",
"which": "^1.0.5"
}
},
"first-match": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/first-match/-/first-match-0.0.1.tgz",
"integrity": "sha1-pg7GQnAPD0NyNOu37D84JHblQv0="
},
"flush-write-stream": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
@ -3788,40 +3838,6 @@
"mime-types": "^2.1.12"
}
},
"foxfire": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/foxfire/-/foxfire-2.0.0.tgz",
"integrity": "sha512-rrulnpfEsyP2/G0rcPs3G6T2A+9RcSpzRGrzcTHZrWXAcBh8PG/nR8J+DyioAbcee+uC1fa/GRCF9tulZy5AeQ==",
"requires": {
"bluebird": "3.5.4",
"cross-spawn-async": "2.2.5",
"dateformat": "3.0.3",
"debug": "4.1.1",
"firefox-launch": "1.1.0",
"firefox-location": "1.0.2",
"mkdirp": "0.5.1"
},
"dependencies": {
"bluebird": {
"version": "3.5.4",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz",
"integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw=="
},
"dateformat": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz",
"integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q=="
},
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
}
}
},
"fragment-cache": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
@ -4509,10 +4525,14 @@
"version": "github:vladikoff/fxa-dev-launcher#f50f8111aa5470d637d1db96050556b5f1b12b2e",
"from": "github:vladikoff/fxa-dev-launcher",
"requires": {
"chalk": "^1.1.3",
"foxfire": "^1.1.0"
"chalk": "^1.1.3"
},
"dependencies": {
"bluebird": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.3.4.tgz",
"integrity": "sha1-94D+Q+GnplEPZ6vX0NeVM6QN3eY="
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
@ -4524,6 +4544,39 @@
"strip-ansi": "^3.0.0",
"supports-color": "^2.0.0"
}
},
"cross-spawn-async": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/cross-spawn-async/-/cross-spawn-async-2.2.1.tgz",
"integrity": "sha1-beTumdaG3rtn+B34WRhCyZL2kHY=",
"requires": {
"lru-cache": "^4.0.0",
"which": "^1.2.4"
}
},
"debug": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
"integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
"requires": {
"ms": "0.7.1"
}
},
"foxfire": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/foxfire/-/foxfire-1.1.0.tgz",
"integrity": "sha1-1z0eAJ4LlkYlyr/zJqIlIjRh9v8=",
"requires": {
"bluebird": "3.3.4",
"cross-spawn-async": "2.2.1",
"dateformat": "1.0.12",
"debug": "2.2.0"
}
},
"ms": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
"integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg="
}
}
},
@ -7740,22 +7793,6 @@
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz",
"integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g="
},
"quick-tmp": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/quick-tmp/-/quick-tmp-0.0.0.tgz",
"integrity": "sha1-QWXmYyDqBfnLzM874fj9X9sF998=",
"requires": {
"first-match": "0.0.1",
"osenv": "0.0.3"
},
"dependencies": {
"osenv": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/osenv/-/osenv-0.0.3.tgz",
"integrity": "sha1-zWrY3bKQkVrZ4idlV2Al1BHynLY="
}
}
},
"raw-body": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
@ -8180,11 +8217,6 @@
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
"integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
},
"shallow-copy": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz",
"integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA="
},
"shebang-command": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
@ -8420,6 +8452,11 @@
"resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
"integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM="
},
"spawn-command": {
"version": "0.0.2-1",
"resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz",
"integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A="
},
"spdx-correct": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
@ -8807,6 +8844,11 @@
"punycode": "^2.1.0"
}
},
"tree-kill": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz",
"integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q=="
},
"trim-newlines": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
@ -9036,11 +9078,6 @@
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
},
"userhome": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/userhome/-/userhome-1.0.0.tgz",
"integrity": "sha1-tkkf8S0hpecmcd+czIcX4cZojAs="
},
"util": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",

Просмотреть файл

@ -25,6 +25,7 @@
"dependencies": {
"adr-log": "^2.1.1",
"chalk": "1.1.1",
"concurrently": "^4.1.1",
"diffparser": "^2.0.1",
"fxa-dev-launcher": "github:vladikoff/fxa-dev-launcher",
"husky": "^2.5.0",

Просмотреть файл

@ -0,0 +1,23 @@
FROM debian:stretch-slim
# FROM debian:stretch # for debugging docker build
RUN \
groupadd --gid 10001 app && \
useradd --uid 10001 --gid 10001 --home /app --create-home app && \
\
apt-get -qq update && \
apt-get -qq install -y default-libmysqlclient-dev libssl-dev ca-certificates && \
update-ca-certificates && \
rm -rf /var/lib/apt/lists
RUN mkdir -p /app
COPY bin /app/bin
COPY config /app/config
COPY version.json /app/version.json
WORKDIR /app/bin
USER app
ENV FXA_EMAIL_ENV production
ENV ROCKET_ENV production
CMD ["/app/bin/fxa_email_send"]