Merge pull request #188 from GSA/main

Demo deploy
This commit is contained in:
Ryan Ahearn
2022-11-15 13:46:41 -05:00
committed by GitHub
27 changed files with 2274 additions and 562 deletions

View File

@@ -1,5 +1,20 @@
# This file is a copy of .gitignore except for file/folders created by the build
# from deploy-exclude.lst
*__pycache__*
.git/*
app/assets/*
bower_components/*
cache/*
.cache/*
node_modules/*
target/*
venv/*
.envrc
.cf/*
.pytest_cache/*
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]

View File

@@ -13,6 +13,9 @@ runs:
uses: actions/setup-python@v3
with:
python-version: "3.9"
- name: Install pipenv
shell: bash
run: pip install --upgrade pipenv
- name: Install application dependencies
shell: bash
run: make bootstrap

View File

@@ -21,21 +21,23 @@ jobs:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-project
- name: Run style checks
run: flake8 .
run: pipenv run flake8 .
- name: Check imports alphabetized
run: isort --check-only ./app ./tests
run: pipenv run isort --check-only ./app ./tests
- name: Run js lint
run: npm run lint
- name: Run js tests
run: npm test
- name: Run py tests
run: pytest -n4 --maxfail=10
run: pipenv run pytest -n4 --maxfail=10
dependency-audits:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-project
- name: Create requirements.txt
run: pipenv requirements > requirements.txt
- uses: trailofbits/gh-action-pip-audit@v1.0.0
with:
inputs: requirements.txt
@@ -48,10 +50,8 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-project
- name: Install bandit
run: pip install bandit
- name: Run scan
run: bandit -r app/ --confidence-level medium
run: pipenv run bandit -r app/ --confidence-level medium
dynamic-scan:
runs-on: ubuntu-latest

View File

@@ -24,6 +24,8 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-project
- name: Create requirements.txt
run: pipenv requirements > requirements.txt
- uses: trailofbits/gh-action-pip-audit@v1.0.0
with:
inputs: requirements.txt
@@ -36,10 +38,8 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-project
- name: Install bandit
run: pip install bandit
- name: Run scan
run: bandit -r app/ --confidence-level medium
run: pipenv run bandit -r app/ --confidence-level medium
dynamic-scan:
runs-on: ubuntu-latest

View File

@@ -40,6 +40,9 @@ jobs:
- uses: ./.github/actions/setup-project
- name: Create requirements.txt because Cloud Foundry does a weird pipenv thing
run: pipenv requirements > requirements.txt
- name: Deploy to cloud.gov
uses: 18f/cg-deploy-action@main
env:

View File

@@ -45,6 +45,9 @@ jobs:
- uses: ./.github/actions/setup-project
- name: Create requirements.txt because Cloud Foundry does a weird pipenv thing
run: pipenv requirements > requirements.txt
- name: Deploy to cloud.gov
uses: 18f/cg-deploy-action@main
env:

146
Makefile
View File

@@ -7,16 +7,6 @@ APP_VERSION_FILE = app/version.py
GIT_BRANCH ?= $(shell git symbolic-ref --short HEAD 2> /dev/null || echo "detached")
GIT_COMMIT ?= $(shell git rev-parse HEAD 2> /dev/null || echo "")
CF_API ?= api.cloud.service.gov.uk
CF_ORG ?= govuk-notify
CF_SPACE ?= ${DEPLOY_ENV}
CF_HOME ?= ${HOME}
CF_APP ?= notify-admin
CF_MANIFEST_PATH ?= /tmp/manifest.yml
$(eval export CF_HOME)
NOTIFY_CREDENTIALS ?= ~/.notify-credentials
VIRTUALENV_ROOT := $(shell [ -z $$VIRTUAL_ENV ] && echo $$(pwd)/venv || echo $$VIRTUAL_ENV)
NVMSH := $(shell [ -f "$(HOME)/.nvm/nvm.sh" ] && echo "$(HOME)/.nvm/nvm.sh" || echo "/usr/local/share/nvm/nvm.sh")
@@ -25,7 +15,7 @@ NVMSH := $(shell [ -f "$(HOME)/.nvm/nvm.sh" ] && echo "$(HOME)/.nvm/nvm.sh" || e
.PHONY: bootstrap
bootstrap: generate-version-file ## Set up everything to run the app
pip3 install -r requirements_for_test.txt
pipenv install --dev
source $(NVMSH) --no-use && nvm install && npm ci --no-audit
source $(NVMSH) && npm run build
@@ -35,7 +25,7 @@ watch-frontend: ## Build frontend and watch for changes
.PHONY: run-flask
run-flask: ## Run flask
flask run -p 6012 --host=0.0.0.0
pipenv run flask run -p 6012 --host=0.0.0.0
.PHONY: npm-audit
npm-audit: ## Check for vulnerabilities in NPM packages
@@ -45,14 +35,6 @@ npm-audit: ## Check for vulnerabilities in NPM packages
help:
@cat $(MAKEFILE_LIST) | grep -E '^[a-zA-Z_-]+:.*?## .*$$' | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
.PHONY: virtualenv
virtualenv:
[ -z $$VIRTUAL_ENV ] && [ ! -d venv ] && python3 -m venv venv || true
.PHONY: upgrade-pip
upgrade-pip: virtualenv
${VIRTUALENV_ROOT}/bin/pip install --upgrade pip
.PHONY: generate-version-file
generate-version-file: ## Generates the app version file
@echo -e "__git_commit__ = \"${GIT_COMMIT}\"\n__time__ = \"${DATE}\"" > ${APP_VERSION_FILE}
@@ -62,12 +44,12 @@ test: py-lint py-test js-lint js-test ## Run tests
.PHONY: py-lint
py-lint: ## Run python linting scanners
flake8 .
isort --check-only ./app ./tests
pipenv run flake8 .
pipenv run isort --check-only ./app ./tests
.PHONY: py-test
py-test: ## Run python unit tests
py.test -n auto --maxfail=10 tests/
pipenv run py.test -n auto --maxfail=10 tests/
.PHONY: js-lint
js-lint: ## Run javascript linting scanners
@@ -79,26 +61,23 @@ js-test: ## Run javascript unit tests
.PHONY: fix-imports
fix-imports: ## Fix imports using isort
isort ./app ./tests
pipenv run isort ./app ./tests
.PHONY: freeze-requirements
freeze-requirements: ## create static requirements.txt
pip install --upgrade pip-tools
pip-compile requirements.in
pipenv requirements > requirements.txt
.PHONY: pip-audit
pip-audit:
pip install --upgrade pip-audit
pip-audit -r requirements.txt -l --ignore-vuln PYSEC-2022-237
-pip-audit -r requirements_for_test.txt -l
pipenv run pip-audit -r requirements.txt -l --ignore-vuln PYSEC-2022-237
-pipenv run pip-audit -r requirements_for_test.txt -l
.PHONY: audit
audit: npm-audit pip-audit
.PHONY: static-scan
static-scan:
pip install bandit
bandit -r app/
pipenv run bandit -r app/
.PHONY: a11y-scan
a11y-scan:
@@ -112,101 +91,22 @@ clean:
## DEPLOYMENT
.PHONY: check-env-vars
check-env-vars: ## Check mandatory environment variables
$(if ${DEPLOY_ENV},,$(error Must specify DEPLOY_ENV))
$(if ${DNS_NAME},,$(error Must specify DNS_NAME))
.PHONY: preview
preview: ## Set environment to preview
$(eval export DEPLOY_ENV=preview)
$(eval export DNS_NAME="notify.works")
@true
.PHONY: staging
staging: ## Set environment to staging
$(eval export DEPLOY_ENV=staging)
$(eval export DNS_NAME="staging-notify.works")
@true
.PHONY: production
production: ## Set environment to production
$(eval export DEPLOY_ENV=production)
$(eval export DNS_NAME="notifications.service.gov.uk")
@true
.PHONY: cf-login
cf-login: ## Log in to Cloud Foundry
$(if ${CF_USERNAME},,$(error Must specify CF_USERNAME))
$(if ${CF_PASSWORD},,$(error Must specify CF_PASSWORD))
$(if ${CF_SPACE},,$(error Must specify CF_SPACE))
@echo "Logging in to Cloud Foundry on ${CF_API}"
@cf login -a "${CF_API}" -u ${CF_USERNAME} -p "${CF_PASSWORD}" -o "${CF_ORG}" -s "${CF_SPACE}"
.PHONY: generate-manifest
generate-manifest:
$(if ${CF_APP},,$(error Must specify CF_APP))
$(if ${CF_SPACE},,$(error Must specify CF_SPACE))
$(if $(shell which gpg2), $(eval export GPG=gpg2), $(eval export GPG=gpg))
$(if ${GPG_PASSPHRASE_TXT}, $(eval export DECRYPT_CMD=echo -n $$$${GPG_PASSPHRASE_TXT} | ${GPG} --quiet --batch --passphrase-fd 0 --pinentry-mode loopback -d), $(eval export DECRYPT_CMD=${GPG} --quiet --batch -d))
@jinja2 --strict manifest.yml.j2 \
-D environment=${CF_SPACE} \
-D CF_APP=${CF_APP} \
--format=yaml \
<(${DECRYPT_CMD} ${NOTIFY_CREDENTIALS}/credentials/${CF_SPACE}/paas/environment-variables.gpg) 2>&1
.PHONY: upload-static ## Upload the static files to be served from S3
upload-static:
aws s3 cp --region us-west-2 --recursive --cache-control max-age=315360000,immutable ./app/static s3://${DNS_NAME}-static
.PHONY: cf-deploy
cf-deploy: ## Deploys the app to Cloud Foundry
$(if ${CF_SPACE},,$(error Must specify CF_SPACE))
@cf app --guid notify-admin || exit 1
# cancel any existing deploys to ensure we can apply manifest (if a deploy is in progress you'll see ScaleDisabledDuringDeployment)
cf cancel-deployment ${CF_APP} || true
# .PHONY: cf-failwhale-deployed
# cf-failwhale-deployed:
# @cf app notify-admin-failwhale --guid || (echo "notify-admin-failwhale is not deployed on ${CF_SPACE}" && exit 1)
# generate manifest (including secrets) and write it to CF_MANIFEST_PATH (in /tmp/)
make -s CF_APP=${CF_APP} generate-manifest > ${CF_MANIFEST_PATH}
# reads manifest from CF_MANIFEST_PATH
CF_STARTUP_TIMEOUT=10 cf push ${CF_APP} --strategy=rolling -f ${CF_MANIFEST_PATH}
# delete old manifest file
rm -f ${CF_MANIFEST_PATH}
# .PHONY: enable-failwhale
# enable-failwhale: cf-target cf-failwhale-deployed ## Enable the failwhale app and disable admin
# @cf map-route notify-admin-failwhale ${DNS_NAME} --hostname www
# @cf unmap-route notify-admin ${DNS_NAME} --hostname www
# @echo "Failwhale is enabled"
.PHONY: cf-deploy-prototype
cf-deploy-prototype: cf-target ## Deploys the first prototype to Cloud Foundry
make -s CF_APP=notify-admin-prototype generate-manifest > ${CF_MANIFEST_PATH}
cf push notify-admin-prototype --strategy=rolling -f ${CF_MANIFEST_PATH}
rm -f ${CF_MANIFEST_PATH}
.PHONY: cf-deploy-prototype-2
cf-deploy-prototype-2: cf-target ## Deploys the second prototype to Cloud Foundry
make -s CF_APP=notify-admin-prototype-2 generate-manifest > ${CF_MANIFEST_PATH}
cf push notify-admin-prototype-2 --strategy=rolling -f ${CF_MANIFEST_PATH}
rm -f ${CF_MANIFEST_PATH}
.PHONY: cf-rollback
cf-rollback: cf-target ## Rollbacks the app to the previous release
cf cancel-deployment ${CF_APP}
rm -f ${CF_MANIFEST_PATH}
.PHONY: cf-target
cf-target: check-env-vars
@cf target -o ${CF_ORG} -s ${CF_SPACE}
.PHONY: cf-failwhale-deployed
cf-failwhale-deployed:
@cf app notify-admin-failwhale --guid || (echo "notify-admin-failwhale is not deployed on ${CF_SPACE}" && exit 1)
.PHONY: enable-failwhale
enable-failwhale: cf-target cf-failwhale-deployed ## Enable the failwhale app and disable admin
@cf map-route notify-admin-failwhale ${DNS_NAME} --hostname www
@cf unmap-route notify-admin ${DNS_NAME} --hostname www
@echo "Failwhale is enabled"
.PHONY: disable-failwhale
disable-failwhale: cf-target cf-failwhale-deployed ## Disable the failwhale app and enable admin
@cf map-route notify-admin ${DNS_NAME} --hostname www
@cf unmap-route notify-admin-failwhale ${DNS_NAME} --hostname www
@echo "Failwhale is disabled"
# .PHONY: disable-failwhale
# disable-failwhale: cf-target cf-failwhale-deployed ## Disable the failwhale app and enable admin
# @cf map-route notify-admin ${DNS_NAME} --hostname www
# @cf unmap-route notify-admin-failwhale ${DNS_NAME} --hostname www
# @echo "Failwhale is disabled"

56
Pipfile Normal file
View File

@@ -0,0 +1,56 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
ago = "==0.0.93"
awscli-cwlogs = ">=1.4,<1.5"
blinker = "==1.4"
fido2 = "==0.9.3"
flask = "==2.1.2"
flask-basicauth = "==0.2.0"
flask-login = "==0.6.1"
flask-wtf = "==1.0.1"
gds-metrics = {version = "==0.2.4", ref = "6f1840a57b6fb1ee40b7e84f2f18ec229de8aa72", git = "https://github.com/alphagov/gds_metrics_python.git"}
govuk-bank-holidays = "==0.11"
govuk-frontend-jinja = {version = "==0.5.8-alpha", git = "https://github.com/alphagov/govuk-frontend-jinja.git"}
gunicorn = {version = "==20.1.0", extras = ["eventlet"], ref = "1299ea9e967a61ae2edebe191082fd169b864c64", git = "https://github.com/benoitc/gunicorn.git"}
humanize = "==4.1.0"
itsdangerous = "==2.1.2"
jinja2 = "==3.1.2"
notifications-python-client = "==6.3.0"
notifications-utils = {version = "==56.0.3", git = "https://github.com/GSA/notifications-utils.git"}
prometheus-client = "==0.14.1"
pyexcel = "==0.7.0"
pyexcel-io = "==0.6.6"
pyexcel-ods3 = "==0.6.1"
pyexcel-xls = "==0.7.0"
pyexcel-xlsx = "==0.6.0"
pyproj = "==3.3.1"
python-dotenv = "==0.20.0"
pytz = "==2022.1"
rtreelib = "==0.2.0"
werkzeug = "==2.1.2"
wtforms = "==3.0.1"
[dev-packages]
isort = "==5.10.1"
pytest = "==7.1.2"
pytest-env = "==0.6.2"
pytest-mock = "==3.7.0"
pytest-xdist = "==2.5.0"
beautifulsoup4 = "==4.11.1"
freezegun = "==1.2.1"
flake8 = "==4.0.1"
flake8-bugbear = "==22.4.25"
flake8-print = "==5.0.0"
moto = "==3.1.7"
requests-mock = "==1.9.3"
# used for creating manifest file locally
jinja2-cli = {version = "==0.8.2", extras = ["yaml"]}
pip-audit = "*"
bandit = "*"
[requires]
python_version = "3.9"

1871
Pipfile.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,43 +1,67 @@
# US Notify Admin
# Notify UI
Cloned from the brilliant work of the team at [GOV.UK Notify](https://github.com/alphagov/notifications-admin), cheers!
This is the Notify front-end for government users and admins. To see it in action, check out [the demo site](https://notify-demo.app.cloud.gov) (contact team for credentials).
US Notify admin application - https://notify-demo.app.cloud.gov (contact team for access)
Through the interface, users can:
- Register and manage users
- Create and manage services
- Send batch emails and SMS by uploading a CSV
- Show history of notifications
- Send batch SMS by uploading a CSV
- View their history of notifications
## QUICKSTART
The [Notify API](https://github.com/GSA/notifications-api) provides the UI's backend and is required for most things to function. Set that up first!
---
**NOTE**: Set up the [notifications-api repo](https://github.com/18F/notifications-api) locally **FIRST**, you'll need both the docker network it provides and a functioning api to make use of the notifications-admin repo. It is expected as a byproduct of getting notifications-api running you will also be running VS Code and the Remote Containers extension, and that docker daemon is running and the API is as well.
## Local setup
Open the notifications-admin repo in VS Code (File->Open Folder, select notifications-admin folder)
### Direct setup
create a .env file as detailed in the .env Setup section below
1. Get the API running
Using VS Code's command pallette (cmd+shift+p), search "Remote Containers: Open folder in Container..."
1. Install [pipenv](https://pipenv.pypa.io/en/latest/)
choose devcontainer-admin folder (note: this is a subfolder of notifications-admin/). This will open a new window, closing the current one in the process. After the new window loads, hit "show logs" link in the bottom-right. If this is the first build it will take a few minutes to create the image. The process completes shortly after running gulp.js and compiling front-end files.
1. Install Python and Node dependencies
Select View->Open View..., then search/select “ports”. Await a green dot on the port view, then open a new terminal and run the web server:
`make run-flask`
`make bootstrap`
Visit [localhost:6012](http://localhost:6012)
1. Create the .env file
NOTE: any .py code changes you make should be picked up automatically in development. If you're developing JavaScript code, open another vscode terminal and run `npm run watch` to achieve the same.
```
cp sample.env .env
# follow the instructions in .env
```
---
## .env Setup
1. Run the Flask server
create a .env file using sample.env as a template
`cp sample.env .env` (or via VS Code file browser)
`make run-flask`
from the notifications-api checkout, copy the values in that repo's .env file for `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` into this repo's .env file.
1. Go to http://localhost:6012
Change `BASIC_AUTH_USERNAME` and `BASIC_AUTH_PASSWORD` to what you'd like them to be for this deployment.
### VS Code && Docker installation
If you're working in VS Code, you can also leverage Docker for a containerized dev environment
1. Get the API running, including the Docker network
1. Create the .env file
```
cp sample.env .env
# follow the instructions in .env
```
1. Install the Remote-Containers plug-in in VS Code
1. Using the command palette (shift+cmd+p) or green button thingy in the bottom left, search and select “Remote Containers: Open Folder in Container...” When prompted, choose **devcontainer-admin** folder (note: this is a *subfolder* of notifications-admin). This will start the container in a new window, replacing the current one.
1. Wait a few minutes while things happen 🍵
1. Open a VS Code terminal and run the Flask application:
`make run-flask`
1. Go to http://localhost:6012
NOTE: when you change .env in the future, you'll need to rebuild the devcontainer for the change to take effect. VS Code _should_ detect the change and prompt you with a toast notification during a cached build. If not, you can find a manual rebuild in command pallette or just `docker rm` the notifications-api container.
## To test the application
From a terminal within the running devcontainer:
@@ -59,7 +83,7 @@ Unlike most of the tests and scans, pa11y-ci cannot currently be run from within
1. Run `make run-flask` from within the devcontainer
2. Run `make a11y-scan` from your host computer.
## Further docs [STILL UK DOCS]
## Further docs from UK
- [Working with static assets](docs/static-assets.md)
- [JavaScript documentation](https://github.com/alphagov/notifications-manuals/wiki/JavaScript-Documentation)
@@ -72,3 +96,11 @@ Work through [commit `543be77`](https://github.com/GSA/notifications-admin/commi
## Contributing
As stated in [CONTRIBUTING.md](CONTRIBUTING.md), all contributions to this project will be released under the CC0 dedication. By submitting a pull request, you are agreeing to comply with this waiver of copyright interest.
## About the TTS Public Benefits Studio
The Public Benefits Studio is a team inside of [GSAs Technology Transformation Services](https://www.gsa.gov/about-us/organization/federal-acquisition-service/technology-transformation-services) (TTS), home to innovative programs like [18F](https://18f.gsa.gov/) and [Login.gov](https://login.gov). We collaborate with benefits programs to develop shared technology tools and best practices that reduce the burden of navigating government programs for low income individuals and families.
Were a cross-functional team of technologists with specialized experience working across public benefits programs like Medicaid, SNAP, and unemployment insurance.
For more information on what we're working on, the Notify tool, and how to get involved with our team, [see our flyer.](https://github.com/GSA/notifications-admin/blob/main/docs/notify-pilot-flyer.md)

View File

@@ -145,11 +145,15 @@ class Production(Config):
class Staging(Production):
BASIC_AUTH_FORCE = True
HEADER_COLOUR = '#6F72AF' # $mauve
HEADER_COLOUR = '#00ff00' # $green
class Demo(Staging):
pass
HEADER_COLOUR = '#6F72AF' # $mauve
class Sandbox(Staging):
HEADER_COLOUR = '#ff0000' # $red
class Scanning(Production):
@@ -167,5 +171,6 @@ configs = {
'scanning': Scanning,
'staging': Staging,
'demo': Demo,
'sandbox': Sandbox,
'production': Production
}

View File

@@ -2,9 +2,8 @@
<h2 class="heading-medium">Links and URLs</h2>
<p class="bottom-gutter-1-3">
Always use full URLs, starting with https://
</p>
Always use full URLs, starting with https://. For example:
{{ govukInsetText({
"text": "Apply now at https://www.gov.uk/example",
"text": "Apply now at https://www.usa.gov/example",
"classes": "govuk-!-margin-top-0"})
}}

View File

@@ -8,7 +8,7 @@
{% block content_column_content %}
<h1 class="heading heading-large">Text messages</h1>
<p class="govuk-body">Send thousands of free text messages to UK and international numbers with US Notify.</p>
<p class="govuk-body">Send thousands of free text messages to US phone numbers with Notify.</p>
{% if not current_user.is_authenticated %}
<p class="govuk-body"><a class="govuk-link govuk-link--no-visited-state" href="{{ url_for('main.register') }}">Create an account</a> and try Notify for yourself.</p>
{% endif %}

View File

@@ -191,7 +191,7 @@
</div>
<h3 class="heading-small" id="international-numbers">Sending text messages to international numbers</h3>
<p class="govuk-body">It might cost more to send text messages to international numbers than UK ones, depending on the country.</p>
<p class="govuk-body">It might cost more to send text messages to international numbers than US ones, depending on the country.</p>
{% set smsIntRates %}
{{ live_search(target_selector='#international-pricing .table-row', show=True, form=search_form, label='Search by country name or code') }}

11
deploy-config/sandbox.yml Normal file
View File

@@ -0,0 +1,11 @@
env: sandbox
instances: 1
memory: 1G
public_admin_route: notify-sandbox.app.cloud.gov
ADMIN_CLIENT_USERNAME: notify-admin
ADMIN_CLIENT_SECRET: dev-notify-secret-key
DANGEROUS_SALT: dev-notify-salt
SECRET_KEY: dev-notify-secret-key
BASIC_AUTH_USERNAME: sandbox
BASIC_AUTH_PASSWORD: sandbox
REDIS_ENABLED: 1

View File

@@ -1,12 +0,0 @@
*__pycache__*
.git/*
app/assets/*
bower_components/*
cache/*
.cache/*
node_modules/*
target/*
venv/*
.envrc
.cf/*
.pytest_cache/*

99
docs/notify-pilot-info.md Normal file
View File

@@ -0,0 +1,99 @@
### The Public Benefits Studio
The Public Benefits Studio is a team inside of GSAs Technology
Transformation Services (TTS), home to innovative programs like 18F and
Login.gov. We collaborate **with benefits programs to develop shared
technology tools and best practices that reduce the burden of navigating
government programs for low income individuals and families.**
Were a cross-functional team of technologists with specialized
experience working across public benefits programs like Medicaid, SNAP,
and unemployment insurance.
### WHAT WERE CURRENTLY EXPLORING
<table>
<colgroup>
<col style="width: 40%" />
<col style="width: 59%" />
</colgroup>
<tbody>
<tr class="odd">
<td><p><strong>SMS &amp; multichannel notifications</strong></p>
<p>Helping individuals and families stay better informed of actions they
need to take related to benefits, such as reminders for upcoming
appointments and enrolling in or recertifying their benefits.</p></td>
<td>According to Pew Research, 97% of US adults with an income of less
than $30,000 have a cellphone, and there have already been <a
href="https://bdtrust.org/nudging-benefits-access-in-the-right-direction/"><u>successful
pilots</u></a> to supplement traditional notification channels with SMS
that both decreased re-enrollment churn and saved the administering
agency money.</td>
</tr>
</tbody>
</table>
OUR FIRST BET: US Notify
<table>
<colgroup>
<col style="width: 67%" />
<col style="width: 32%" />
</colgroup>
<tbody>
<tr class="odd">
<td><h3 id="section"><img src="https://user-images.githubusercontent.com/6556888/200647299-b991a5ed-ecf4-4d74-b238-f3def9bd1503.png"
style="width:4.63788in;height:3.75964in" /></h3></td>
<td><h3 id="create-custom-content">Create custom content</h3>
<p>No technical knowledge needed to create direct, actionable
messages.</p>
<h3 id="send-bulk-messages">Send bulk messages </h3>
<p>Upload a .csv file with necessary information and Notify sends
messages customized to each recipient</p>
<h3 id="see-how-messages-perform">See how messages perform</h3>
<p>Track how many messages youve sent and monitor successful delivery
rates</p></td>
</tr>
</tbody>
</table>
**US Notify** will be a federally-run shared service allowing staff to
send thousands of customized-to-the-user text messages per year at
little-to-no-cost. The easy interface requires no technical expertise to
use and the setup process takes only ten minutes.
### WHERE WERE AT
**Were in the early stages of assessing this products market fit and
targeting to pilot** this shared service with at least 3 partners in
spring of 2023. Following our first pilot, we intend to scale by adding
additional features based on partner needs.
| | | |
|-------------------------------------------------------------------------|---------------------------------------------------------------------------|---------------------------------------------------------------------------------|
| **Pilot Launch Features** | **Priority Future Features** | **Possible Future Features** |
| Bulk, individually customizable SMS sending via web UI | SMS sending via API integration | Email sending via UI and API |
| Organization permissions settings for various team members to edit/send | Single-level decision bidirectionality (e.g. reply “YES” if, or “NO” if…) | Multiple-level decision bidirectionality (greater than one layer decision-tree) |
| Reusable message templates | Self-service account creation | Open-text reply bidirectionality (rather than reply yes or no, 1 or 2, etc.) |
| Message send/failure analytics | Application status page | Multilingual interface and content library options |
| 7-day records deletion | Scheduled send option | Recurring scheduled send |
### OPPORTUNITIES TO GET INVOLVED
To get involved, email us at [tts-benefits-studio@gsa.gov](mailto:tts-benefits-studio@gsa.gov) with the following in the subject line!
1. **Request a Studio introduction and/or a Notify demo:** Request an
introductory call to
talk about our product, near term goals, and ways to get involved.
2. **Provide feedback about Notify:** If you're a potential product
user, set up an individual feedback session to help us fortify our
tool and talk through potential set-up considerations from your
vantage point.
3. **Sign up to pilot Notify:** Already think this might be the tool
for you? Were looking for benefit-administering partners to
co-design a pilot program to test US Notify for specific use cases.
Early adopters will have wrap-around set-up support from the Studio
and an opportunity to shape the future of this product.

View File

@@ -1,65 +0,0 @@
{%- set apps = {
'notify-admin': {
'routes': {
'preview': ['www.notify.works', 'notify-admin-preview.apps.internal'],
'staging': ['www.staging-notify.works', 'notify-admin-staging.apps.internal'],
'production': ['www.notifications.service.gov.uk', 'notify-admin-production.apps.internal'],
}
},
'notify-admin-prototype': {},
'notify-admin-prototype-2': {}
} -%}
{%- set app = apps[CF_APP] -%}
---
applications:
- name: {{ CF_APP }}
buildpack: python_buildpack
memory: 1G
routes:
- route: {{ CF_APP }}-{{ environment }}.cloudapps.digital
{%- for route in app.get('routes', {}).get(environment, []) %}
- route: {{ route }}
{%- endfor %}
health-check-type: http
health-check-http-endpoint: '/_status?simple=true'
health-check-invocation-timeout: 10
services:
- logit-ssl-syslog-drain
- notify-prometheus
- notify-splunk
- notify-redis
env:
NOTIFY_APP_NAME: admin
NOTIFY_LOG_PATH: /home/vcap/logs/app.log
FLASK_APP: application.py
NOTIFY_ENVIRONMENT: {{ environment }}
# Credentials variables
ADMIN_CLIENT_SECRET: '{{ ADMIN_CLIENT_SECRET }}'
ADMIN_BASE_URL: '{{ ADMIN_BASE_URL }}'
API_HOST_NAME: '{{ API_HOST_NAME }}'
DANGEROUS_SALT: '{{ DANGEROUS_SALT }}'
SECRET_KEY: '{{ SECRET_KEY }}'
ROUTE_SECRET_KEY_1: '{{ ROUTE_SECRET_KEY_1 }}'
ROUTE_SECRET_KEY_2: '{{ ROUTE_SECRET_KEY_2 }}'
METRICS_BASIC_AUTH_TOKEN: {{ METRICS_BASIC_AUTH_TOKEN }}
AWS_ACCESS_KEY_ID: '{{ AWS_ACCESS_KEY_ID }}'
AWS_SECRET_ACCESS_KEY: '{{ AWS_SECRET_ACCESS_KEY }}'
ANTIVIRUS_API_HOST: '{{ ANTIVIRUS_API_HOST }}'
ANTIVIRUS_API_KEY: '{{ ANTIVIRUS_API_KEY }}'
ZENDESK_API_KEY: '{{ ZENDESK_API_KEY }}'
TEMPLATE_PREVIEW_API_HOST: '{{ TEMPLATE_PREVIEW_API_HOST }}'
TEMPLATE_PREVIEW_API_KEY: '{{ TEMPLATE_PREVIEW_API_KEY }}'
NOTIFY_BILLING_DETAILS: '{{ NOTIFY_BILLING_DETAILS | tojson }}'

View File

@@ -1,38 +0,0 @@
# Run `make freeze-requirements` to update requirements.txt
# with package version changes made in requirements-app.txt
ago==0.0.93
govuk-bank-holidays==0.11
humanize==4.1.0
Flask==2.1.2
Flask-WTF==1.0.1
wtforms==3.0.1
Flask-Login==0.6.1
Werkzeug==2.1.2
jinja2==3.1.2
Flask-BasicAuth==0.2.0
blinker==1.4
pyexcel==0.7.0
pyexcel-io==0.6.6
pyexcel-xls==0.7.0
pyexcel-xlsx==0.6.0
pyexcel-ods3==0.6.1
pytz==2022.1
# Should be pinned until a new gunicorn release greater than 20.1.0 comes out. (Due to eventlet v0.33 compatibility issues)
git+https://github.com/benoitc/gunicorn.git@1299ea9e967a61ae2edebe191082fd169b864c64#egg=gunicorn[eventlet]==20.1.0
notifications-python-client==6.3.0
rtreelib==0.2.0
fido2==0.9.3
pyproj==3.3.1
python-dotenv==0.20.0
# PaaS
awscli-cwlogs>=1.4,<1.5
itsdangerous==2.1.2
notifications-utils @ git+https://github.com/GSA/notifications-utils.git@s3-credentials
govuk-frontend-jinja @ git+https://github.com/alphagov/govuk-frontend-jinja.git@v0.5.8-alpha
# gds-metrics requires prometheseus 0.2.0, override that requirement as later versions bring significant performance gains
prometheus-client==0.14.1
git+https://github.com/alphagov/gds_metrics_python.git@6f1840a57b6fb1ee40b7e84f2f18ec229de8aa72

View File

@@ -1,248 +0,0 @@
#
# This file is autogenerated by pip-compile with python 3.9
# To update, run:
#
# pip-compile requirements.in
#
ago==0.0.93
# via -r requirements.in
async-timeout==4.0.2
# via redis
awscli==1.24.8
# via awscli-cwlogs
awscli-cwlogs==1.4.6
# via -r requirements.in
bleach==5.0.0
# via notifications-utils
blinker==1.4
# via
# -r requirements.in
# gds-metrics
boto3==1.23.8
# via notifications-utils
botocore==1.26.8
# via
# awscli
# boto3
# s3transfer
cachetools==5.1.0
# via notifications-utils
certifi==2022.5.18.1
# via
# pyproj
# requests
cffi==1.15.0
# via cryptography
chardet==4.0.0
# via pyexcel
charset-normalizer==2.0.12
# via requests
click==8.1.3
# via flask
colorama==0.4.4
# via awscli
cryptography==38.0.3
# via fido2
deprecated==1.2.13
# via redis
dnspython==2.2.1
# via eventlet
docopt==0.6.2
# via notifications-python-client
docutils==0.16
# via awscli
et-xmlfile==1.1.0
# via openpyxl
eventlet==0.33.1
# via gunicorn
fido2==0.9.3
# via -r requirements.in
flask==2.1.2
# via
# -r requirements.in
# flask-basicauth
# flask-login
# flask-redis
# flask-wtf
# gds-metrics
# notifications-utils
flask-basicauth==0.2.0
# via -r requirements.in
flask-login==0.6.1
# via -r requirements.in
flask-redis==0.4.0
# via notifications-utils
flask-wtf==1.0.1
# via -r requirements.in
gds-metrics @ git+https://github.com/alphagov/gds_metrics_python.git@6f1840a57b6fb1ee40b7e84f2f18ec229de8aa72
# via -r requirements.in
geojson==2.5.0
# via notifications-utils
govuk-bank-holidays==0.11
# via
# -r requirements.in
# notifications-utils
govuk-frontend-jinja @ git+https://github.com/alphagov/govuk-frontend-jinja.git@v0.5.8-alpha
# via -r requirements.in
greenlet==1.1.2
# via eventlet
gunicorn @ git+https://github.com/benoitc/gunicorn.git@1299ea9e967a61ae2edebe191082fd169b864c64
# via -r requirements.in
humanize==4.1.0
# via -r requirements.in
idna==3.3
# via requests
importlib-metadata==4.12.0
# via flask
itsdangerous==2.1.2
# via
# -r requirements.in
# flask
# flask-wtf
# notifications-utils
jinja2==3.1.2
# via
# -r requirements.in
# flask
# govuk-frontend-jinja
# notifications-utils
jmespath==1.0.0
# via
# boto3
# botocore
lml==0.1.0
# via
# pyexcel
# pyexcel-io
lxml==4.9.1
# via
# pyexcel-ezodf
# pyexcel-ods3
markupsafe==2.1.1
# via
# jinja2
# wtforms
mistune==0.8.4
# via notifications-utils
notifications-python-client==6.3.0
# via -r requirements.in
notifications-utils @ git+https://github.com/GSA/notifications-utils.git@s3-credentials
# via -r requirements.in
openpyxl==3.0.10
# via pyexcel-xlsx
orderedset==2.0.3
# via notifications-utils
packaging==21.3
# via redis
phonenumbers==8.12.48
# via notifications-utils
prometheus-client==0.14.1
# via
# -r requirements.in
# gds-metrics
pyasn1==0.4.8
# via rsa
pycparser==2.21
# via cffi
pyexcel==0.7.0
# via -r requirements.in
pyexcel-ezodf==0.3.4
# via pyexcel-ods3
pyexcel-io==0.6.6
# via
# -r requirements.in
# pyexcel
# pyexcel-ods3
# pyexcel-xls
# pyexcel-xlsx
pyexcel-ods3==0.6.1
# via -r requirements.in
pyexcel-xls==0.7.0
# via -r requirements.in
pyexcel-xlsx==0.6.0
# via -r requirements.in
pyjwt==2.4.0
# via notifications-python-client
pyparsing==3.0.9
# via packaging
pypdf2==2.0.0
# via notifications-utils
pyproj==3.3.1
# via
# -r requirements.in
# notifications-utils
python-dateutil==2.8.2
# via
# awscli-cwlogs
# botocore
python-dotenv==0.20.0
# via -r requirements.in
python-json-logger==2.0.2
# via notifications-utils
pytz==2022.1
# via
# -r requirements.in
# notifications-utils
pyyaml==5.4.1
# via
# awscli
# notifications-utils
redis==4.3.1
# via flask-redis
requests==2.27.1
# via
# awscli-cwlogs
# govuk-bank-holidays
# notifications-python-client
# notifications-utils
rsa==4.7.2
# via awscli
rtreelib==0.2.0
# via -r requirements.in
s3transfer==0.5.2
# via
# awscli
# boto3
shapely==1.8.2
# via notifications-utils
six==1.16.0
# via
# awscli-cwlogs
# bleach
# eventlet
# fido2
# python-dateutil
smartypants==2.0.1
# via notifications-utils
statsd==3.3.0
# via notifications-utils
texttable==1.6.4
# via pyexcel
typing-extensions==4.3.0
# via pypdf2
urllib3==1.26.9
# via
# botocore
# requests
webencodings==0.5.1
# via bleach
werkzeug==2.1.2
# via
# -r requirements.in
# flask
# flask-login
wrapt==1.14.1
# via deprecated
wtforms==3.0.1
# via
# -r requirements.in
# flask-wtf
xlrd==2.0.1
# via pyexcel-xls
xlwt==1.3.0
# via pyexcel-xls
zipp==3.8.1
# via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
# setuptools

View File

@@ -1,15 +0,0 @@
-r requirements.txt
isort==5.10.1
pytest==7.1.2
pytest-env==0.6.2
pytest-mock==3.7.0
pytest-xdist==2.5.0
beautifulsoup4==4.11.1
freezegun==1.2.1
flake8==4.0.1
flake8-bugbear==22.4.25
flake8-print==5.0.0
moto==3.1.7
requests-mock==1.9.3
# used for creating manifest file locally
jinja2-cli[yaml]==0.8.2

View File

@@ -1,5 +1,24 @@
# STEPS TO SET UP
#
# 1. Pull down AWS creds from cloud.gov using `cf env`, then update AWS section
#
# 2. Uncomment either the Docker setup or the direct setup
#
# 3. Comment out the other setup
#
# ## REBUILD THE DEVCONTAINER WHEN YOU MODIFY .ENV ###
#############################################################
# AWS
AWS_REGION=us-west-2
AWS_ACCESS_KEY_ID="don't write secrets to the sample file"
AWS_SECRET_ACCESS_KEY="don't write secrets to the sample file"
#############################################################
# Application
NOTIFY_ENVIRONMENT=development
FLASK_APP=application.py
FLASK_ENV=development
@@ -8,11 +27,12 @@ WERKZEUG_DEBUG_PIN=off
ANTIVIRUS_ENABLED=0
NODE_VERSION=16.15.1
# URL of api app (on AWS this is the internal api endpoint)
API_HOST_NAME=http://dev:6011
REDIS_URL=redis://adminredis:6379/0
#############################################################
# AWS
AWS_REGION=us-west-2
AWS_ACCESS_KEY_ID="don't write secrets to the sample file"
AWS_SECRET_ACCESS_KEY="don't write secrets to the sample file"
# Local Docker setup
# API_HOST_NAME=http://dev:6011
# REDIS_URL=redis://adminredis:6379/0
# Local direct setup
API_HOST_NAME=http://localhost:6011
REDIS_URL=redis://localhost:6379/0

View File

@@ -7,14 +7,18 @@ $0: Create a Service User Account for a given space
Usage:
$0 -h
$0 -s <SPACE NAME> -u <USER NAME> [-r <ROLE NAME>] [-o <ORG NAME>]
$0 -s <SPACE NAME> -u <USER NAME> [-r <ROLE NAME>] [-o <ORG NAME>] [-m]
Options:
-h: show help and exit
-s <SPACE NAME>: configure the space to act on. Required
-u <USER NAME>: set the service user name. Required
-r <ROLE NAME>: set the service user's role to either space-deployer or space-auditor. Default: space-deployer
-m: If provided, make the service user an OrgManager
-o <ORG NAME>: configure the organization to act on. Default: $org
Notes:
OrgManager is required for terraform to create <env>-egress spaces
"
set -e
@@ -23,8 +27,9 @@ set -o pipefail
space=""
service=""
role="space-deployer"
org_manager="false"
while getopts ":hs:u:r:o:" opt; do
while getopts ":hms:u:r:o:" opt; do
case "$opt" in
s)
space=${OPTARG}
@@ -38,6 +43,9 @@ while getopts ":hs:u:r:o:" opt; do
o)
org=${OPTARG}
;;
m)
org_manager="true"
;;
h)
echo "$usage"
exit 0
@@ -60,13 +68,17 @@ cf create-service-key $service service-account-key 1>&2
# output service key to stdout in secrets.auto.tfvars format
creds=`cf service-key $service service-account-key | tail -n 4`
username=`echo $creds | jq '.username'`
password=`echo $creds | jq '.password'`
username=`echo $creds | jq -r '.username'`
password=`echo $creds | jq -r '.password'`
if [[ $org_manager = "true" ]]; then
cf set-org-role $username $org OrgManager 1>&2
fi
cat << EOF
# generated with $0 -s $space -u $service -r $role -o $org
# revoke with $(dirname $0)/destroy_service_account.sh -s $space -u $service -o $org
cf_user = $username
cf_password = $password
cf_user = "$username"
cf_password = "$password"
EOF

48
terraform/sandbox/main.tf Normal file
View File

@@ -0,0 +1,48 @@
locals {
cf_org_name = "gsa-tts-benefits-studio-prototyping"
cf_space_name = "notify-sandbox"
env = "sandbox"
app_name = "notify-admin"
recursive_delete = true
}
module "redis" {
source = "github.com/18f/terraform-cloudgov//redis"
cf_user = var.cf_user
cf_password = var.cf_password
cf_org_name = local.cf_org_name
cf_space_name = local.cf_space_name
env = local.env
app_name = local.app_name
recursive_delete = local.recursive_delete
redis_plan_name = "redis-dev"
}
module "logo_upload_bucket" {
source = "github.com/18f/terraform-cloudgov//s3"
cf_user = var.cf_user
cf_password = var.cf_password
cf_org_name = local.cf_org_name
cf_space_name = local.cf_space_name
recursive_delete = local.recursive_delete
s3_service_name = "${local.app_name}-logo-upload-bucket-${local.env}"
}
# ##########################################################################
# The following lines need to be commented out for the initial `terraform apply`
# It can be re-enabled after:
# 1) the api app has first been deployed
# 2) the admin app has first been deployed
###########################################################################
# module "api_network_route" {
# source = "../shared/container_networking"
# cf_user = var.cf_user
# cf_password = var.cf_password
# cf_org_name = local.cf_org_name
# cf_space_name = local.cf_space_name
# source_app_name = "${local.app_name}-${local.env}"
# destination_app_name = "notify-api-${local.env}"
# }

View File

@@ -0,0 +1,17 @@
terraform {
required_version = "~> 1.0"
required_providers {
cloudfoundry = {
source = "cloudfoundry-community/cloudfoundry"
version = "0.15.5"
}
}
backend "s3" {
bucket = "cg-6b759c13-6253-4a64-9bda-dd1f620185b0"
key = "admin.tfstate.sandbox"
encrypt = "true"
region = "us-gov-west-1"
profile = "notify-terraform-backend"
}
}

View File

@@ -0,0 +1,5 @@
variable "cf_password" {
type = string
sensitive = true
}
variable "cf_user" {}

View File

@@ -1,9 +0,0 @@
ADMIN_CLIENT_SECRET: asdf
DANGEROUS_SALT: asdf
SECRET_KEY: asdf
ROUTE_SECRET_KEY_1: asdf
ROUTE_SECRET_KEY_2: asdf
AWS_ACCESS_KEY_ID: asdf
AWS_SECRET_ACCESS_KEY: asdf
BASIC_AUTH_USERNAME: asdf
BASIC_AUTH_PASSWORD: asdf