From 412575c81d20e4d9b741eb2925461453d0b2611b Mon Sep 17 00:00:00 2001 From: stvnrlly Date: Wed, 19 Oct 2022 13:13:50 -0400 Subject: [PATCH 01/19] no longer using pyup --- .pyup.yml | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 .pyup.yml diff --git a/.pyup.yml b/.pyup.yml deleted file mode 100644 index 2dec6592b..000000000 --- a/.pyup.yml +++ /dev/null @@ -1,8 +0,0 @@ -# see https://pyup.io/docs/configuration/ for all available options - -schedule: "every week on wednesday" - -search: False -requirements: - - requirements.in - - requirements_for_test.txt From 9df489d03a620f3a63cef4d4647555bb511e5e83 Mon Sep 17 00:00:00 2001 From: stvnrlly Date: Thu, 20 Oct 2022 14:03:52 -0400 Subject: [PATCH 02/19] remove unnecessary files --- dump.rdb | Bin 88 -> 0 bytes varsfile.sample | 30 ------------------------------ 2 files changed, 30 deletions(-) delete mode 100644 dump.rdb delete mode 100644 varsfile.sample diff --git a/dump.rdb b/dump.rdb deleted file mode 100644 index 6ec88245cd38ae9dd6d5f1433877fbb343911e97..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88 zcmWG?b@2=~FfcUu#aWb^l3A=P;l-K50g)B_CvH=CNW&V0YN9{|UdBq0C* diff --git a/varsfile.sample b/varsfile.sample deleted file mode 100644 index 9cd9a537a..000000000 --- a/varsfile.sample +++ /dev/null @@ -1,30 +0,0 @@ -SECRET_KEY: "dev-notify-secret-key" # pragma: allowlist secret -DANGEROUS_SALT: "dev-notify-salt" - -ADMIN_BASE_URL: https://notifications-admin.app.cloud.gov -ADMIN_CLIENT_ID: notify-admin -ADMIN_CLIENT_SECRET: dev-notify-secret-key -API_HOST_NAME: https://notifications-api.app.cloud.gov -AWS_PINPOINT_REGION: us-west-2 -AWS_REGION: us-west-2 -AWS_US_TOLL_FREE_NUMBER: 18446120782 -DANGEROUS_SALT: dev-notify-salt -DVLA_EMAIL_ADDRESSES: [] -FIRETEXT_API_KEY: placeholder -FIRETEXT_INBOUND_SMS_AUTH: {} -FIRETEXT_INTERNATIONAL_API_KEY: placeholder -FLASK_APP: application.py -FLASK_ENV: production -INTERNAL_CLIENT_API_KEYS: '{"notify-admin":["dev-notify-secret-key"]}' -MMG_API_KEY: placeholder -MMG_INBOUND_SMS_AUTH: {} -MMG_INBOUND_SMS_USERNAME: {} -NOTIFICATION_QUEUE_PREFIX: prototype_10x -NOTIFY_APP_NAME: api -NOTIFY_EMAIL_DOMAIN: dispostable.com -NOTIFY_ENVIRONMENT: live -NOTIFY_LOG_PATH: /home/vcap/logs/app.log -ROUTE_SECRET_KEY_1: dev-route-secret-key-1 -ROUTE_SECRET_KEY_2: dev-route-secret-key-2 -SECRET_KEY: dev-notify-secret-key -STATSD_HOST: localhost From cb9e098c6507d4ca15f94889e4cca872516e3b77 Mon Sep 17 00:00:00 2001 From: stvnrlly Date: Thu, 20 Oct 2022 14:04:10 -0400 Subject: [PATCH 03/19] streamline makefile --- Makefile | 92 +++++++++++++++----------------------------------------- 1 file changed, 24 insertions(+), 68 deletions(-) diff --git a/Makefile b/Makefile index 701ae3380..1de8c9589 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +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) -CF_API ?= api.cloud.service.gov.uk -CF_ORG ?= govuk-notify CF_SPACE ?= ${DEPLOY_ENV} CF_HOME ?= ${HOME} $(eval export CF_HOME) @@ -90,24 +88,6 @@ clean: ## DEPLOYMENT -.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)) @@ -116,64 +96,40 @@ cf-login: ## Log in to Cloud Foundry @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: cf-deploy -cf-deploy: ## Deploys the app to Cloud Foundry - $(if ${CF_SPACE},,$(error Must specify CF_SPACE)) - $(if ${CF_APP},,$(error Must specify CF_APP)) - cf target -o ${CF_ORG} -s ${CF_SPACE} - @cf app --guid ${CF_APP} || 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 - - # fails after 15 mins if deploy doesn't work - CF_STARTUP_TIMEOUT=15 cf push ${CF_APP} --strategy=rolling - -.PHONY: cf-deploy-api-db-migration -cf-deploy-api-db-migration: - $(if ${CF_SPACE},,$(error Must specify CF_SPACE)) - cf target -o ${CF_ORG} -s ${CF_SPACE} - make -s CF_APP=notifications-api generate-manifest > ${CF_MANIFEST_PATH} - - cf push notifications-api --no-route -f ${CF_MANIFEST_PATH} - rm ${CF_MANIFEST_PATH} - - cf run-task notifications-api --command="flask db upgrade" --name api_db_migration - .PHONY: cf-check-api-db-migration-task cf-check-api-db-migration-task: ## Get the status for the last notifications-api task @cf curl /v3/apps/`cf app --guid notifications-api`/tasks?order_by=-created_at | jq -r ".resources[0].state" -.PHONY: cf-rollback -cf-rollback: ## Rollbacks the app to the previous release - $(if ${CF_APP},,$(error Must specify CF_APP)) - rm ${CF_MANIFEST_PATH} - cf cancel-deployment ${CF_APP} +# .PHONY: cf-rollback +# cf-rollback: ## Rollbacks the app to the previous release +# $(if ${CF_APP},,$(error Must specify CF_APP)) +# rm ${CF_MANIFEST_PATH} +# cf cancel-deployment ${CF_APP} .PHONY: check-if-migrations-to-run check-if-migrations-to-run: @echo $(shell python3 scripts/check_if_new_migration.py) -.PHONY: cf-deploy-failwhale -cf-deploy-failwhale: - $(if ${CF_SPACE},,$(error Must target space, eg `make preview cf-deploy-failwhale`)) - cd ./paas-failwhale; cf push notify-api-failwhale -f manifest.yml +# .PHONY: cf-deploy-failwhale +# cf-deploy-failwhale: +# $(if ${CF_SPACE},,$(error Must target space, eg `make preview cf-deploy-failwhale`)) +# cd ./paas-failwhale; cf push notify-api-failwhale -f manifest.yml -.PHONY: enable-failwhale -enable-failwhale: ## Enable the failwhale app and disable api - $(if ${DNS_NAME},,$(error Must target space, eg `make preview enable-failwhale`)) - # make sure failwhale is running first - cf start notify-api-failwhale +# .PHONY: enable-failwhale +# enable-failwhale: ## Enable the failwhale app and disable api +# $(if ${DNS_NAME},,$(error Must target space, eg `make preview enable-failwhale`)) +# # make sure failwhale is running first +# cf start notify-api-failwhale - cf map-route notify-api-failwhale ${DNS_NAME} --hostname api - cf unmap-route notify-api ${DNS_NAME} --hostname api - @echo "Failwhale is enabled" +# cf map-route notify-api-failwhale ${DNS_NAME} --hostname api +# cf unmap-route notify-api ${DNS_NAME} --hostname api +# @echo "Failwhale is enabled" -.PHONY: disable-failwhale -disable-failwhale: ## Disable the failwhale app and enable api - $(if ${DNS_NAME},,$(error Must target space, eg `make preview disable-failwhale`)) +# .PHONY: disable-failwhale +# disable-failwhale: ## Disable the failwhale app and enable api +# $(if ${DNS_NAME},,$(error Must target space, eg `make preview disable-failwhale`)) - cf map-route notify-api ${DNS_NAME} --hostname api - cf unmap-route notify-api-failwhale ${DNS_NAME} --hostname api - cf stop notify-api-failwhale - @echo "Failwhale is disabled" +# cf map-route notify-api ${DNS_NAME} --hostname api +# cf unmap-route notify-api-failwhale ${DNS_NAME} --hostname api +# cf stop notify-api-failwhale +# @echo "Failwhale is disabled" From 2ce19fd502ac49c4c0b9d2b9ee3967ced7de9b4d Mon Sep 17 00:00:00 2001 From: stvnrlly Date: Thu, 20 Oct 2022 14:04:38 -0400 Subject: [PATCH 04/19] add env check before purging data in command --- app/commands.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/commands.py b/app/commands.py index 0515a755f..7f85718e8 100644 --- a/app/commands.py +++ b/app/commands.py @@ -113,6 +113,10 @@ def purge_functional_test_data(user_email_prefix): users, services, etc. Give an email prefix. Probably "notify-tests-preview". """ + if os.getenv('NOTIFY_ENVIRONMENT', '') not in ['development', 'test']: + current_app.logger.error('Can only be run in development') + return + users = User.query.filter(User.email_address.like("{}%".format(user_email_prefix))).all() for usr in users: # Make sure the full email includes a uuid in it From a45e02d6e5b1c8da52dc03d2ab370ed7467312f8 Mon Sep 17 00:00:00 2001 From: stvnrlly Date: Thu, 20 Oct 2022 14:05:23 -0400 Subject: [PATCH 05/19] restructure readme & docs --- README.md | 182 ++++++++++++------------------------ docs/database-management.md | 55 +++++++++++ docs/infra-onboarding.md | 7 ++ docs/infra-setup.md | 20 ++++ docs/one-off-tasks.md | 22 +++++ docs/testing.md | 31 ++++++ 6 files changed, 193 insertions(+), 124 deletions(-) create mode 100644 docs/database-management.md create mode 100644 docs/infra-onboarding.md create mode 100644 docs/infra-setup.md create mode 100644 docs/one-off-tasks.md create mode 100644 docs/testing.md diff --git a/README.md b/README.md index f23ed0c83..cb0a95a21 100644 --- a/README.md +++ b/README.md @@ -1,153 +1,87 @@ # US Notify API -Cloned from the brilliant work of the team at [GOV.UK Notify](https://github.com/alphagov/notifications-api), cheers! +This project is the core of [Notify](https://notifications-admin.app.cloud.gov/). It's cloned from the brilliant work of the team at [GOV.UK Notify](https://github.com/alphagov/notifications-api), cheers! -Contains: +This repo contains: -- the public-facing REST API for US Notify, which teams can integrate with using [our clients](https://www.notifications.service.gov.uk/documentation) [DOCS ARE STILL UK] -- an internal-only REST API built using Flask to manage services, users, templates, etc (this is what the [admin app](http://github.com/18F/notifications-admin) talks to) -- asynchronous workers built using Celery to put things on queues and read them off to be processed, sent to providers, updated, etc +- A public-facing REST API for Notify, which teams can integrate with using [API clients built by UK](https://www.notifications.service.gov.uk/documentation) +- An internal-only REST API built using Flask to manage services, users, templates, etc., which the [admin UI](http://github.com/18F/notifications-admin) talks to) +- Asynchronous workers built using Celery to put things on queues and read them off to be processed, sent to providers, updated, etc -## QUICKSTART ---- -If you are the first on your team to deploy, set up AWS SES/SNS as instructed in the AWS setup section below. +## Local setup -Create .env file as described in the .env section below. +### Direct installation -Install VS Code -Open VS Code and install the Remote-Containers plug-in from Microsoft. +1. Set up Postgres && Redis -Make sure your docker daemon is running (on OS X, this is typically accomplished by opening the Docker Desktop app) -Also make sure there is NOT a Postgres daemon running on port 5432. +1. Install dependencies into a virtual environment -Create the external docker network: + ``` + pipenv install --with dev + createdb notification_api + flask db upgrade + ``` -`docker network create notify-network` +1. Create the .env file -Using the command palette (shift+cmd+p), search and select “Remote Containers: Open Folder in Container...” -When prompted, choose **devcontainer-api** folder (note: this is a *subfolder* of notification-api). This will startup the container in a new window (replacing the current one). + ``` + cp sample.env .env + # follow the instructions in .env + ``` -After this page loads, hit "show logs” in bottom-right. The first time this runs it will need to build the Docker image, which will likely take several minutes. +1. Run Flask -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` + ``` + pipenv run make run-flask + ``` -Open another terminal and run the background tasks: -`make run-celery` +1. Run Celery -Confirm that everything is working by hitting localhost:6011 and it responds with a 200 OK. + ``` + pipenv run make run-celery + ``` ---- -## Setting Up -### `.env` file +### VS Code && Docker installation -Create and edit a .env file, based on sample.env. +If you're working in VS Code, you can also leverage Docker for a containerized dev environment + +1. Create .env file as described in the .env section below. + +1. Install the Remote-Containers plug-in in VS Code + +1. With Docker running, create the network: + + `docker network create notify-network` + +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-api** folder (note: this is a *subfolder* of notification-api). This will startup 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. Open another VS Code terminal and run Celery: + + `make run-celery` NOTE: when you change .env in the future, you'll need to rebuild the devcontainer for the change to take effect. Vscode _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. -Things to change: +## Deeper documentation -- If you're not the first to deploy, only replace the aws creds, get these from team lead -- Replace `NOTIFY_EMAIL_DOMAIN` with the domain your emails will come from (i.e. the "origination email" in your SES project) -- Replace `SECRET_KEY` and `DANGEROUS_SALT` with high-entropy secret values -- Set up AWS SES and SNS as indicated in next section (AWS Setup), fill in missing AWS env vars +### Infrastructure -### AWS Setup +- [Checklist for onboarding to all of the things](./docs/infra-onboarding.md) +- [Setting up the initial infrastructure using AWS](./docs/infra-setup.md) +- [Database management](./docs/database-management.md) -**Steps to prepare SES** +### Common dev work -1. Go to SES console for \$AWS_REGION and create new origin and destination emails. AWS will send a verification via email which you'll need to complete. -2. Find and replace instances in the repo of "testsender", "testreceiver" and "dispostable.com", with your origin and destination email addresses, which you verified in step 1 above. +- [Testing](./docs/testing.md) +- [Running one-off tasks](./docs/one-off-tasks.md) -TODO: create env vars for these origin and destination email addresses for the root service, and create new migrations to update postgres seed fixtures - -**Steps to prepare SNS** - -1. Go to Pinpoints console for \$AWS_PINPOINT_REGION and choose "create new project", then "configure for sms" -2. Tick the box at the top to enable SMS, choose "transactional" as the default type and save -3. In the lefthand sidebar, go the "SMS and Voice" (bottom) and choose "Phone Numbers" -4. Under "Number Settings" choose "Request Phone Number" -5. Choose Toll-free number, tick SMS, untick Voice, choose "transactional", hit next and then "request" -6. Go to SNS console for \$AWS_PINPOINT_REGION, look at lefthand sidebar under "Mobile" and go to "Text Messaging (SMS)" -7. Scroll down to "Sandbox destination phone numbers" and tap "Add phone number" then follow the steps to verify (you'll need to be able to retrieve a code sent to each number) - -At this point, you _should_ be able to complete both the email and phone verification steps of the Notify user sign up process! 🎉 - -### Secrets Detection - -``` -brew install detect-secrets # or pip install detect-secrets -detect-secrets scan -#review output of above, make sure none of the baseline entries are sensitive -detect-secrets scan > .secrets.baseline -#creates the baseline file -``` - -Ideally, you'll install `detect-secrets` so that it's accessible from any environment from which you _might_ commit. You can use `brew install` to make it available globally. You could also install via `pip install` inside a virtual environment, if you're sure you'll _only_ commit from that environment. - -If you open .git/hooks/pre-commit you should see a simple bash script that runs the command below, reads the output and aborts before committing if detect-secrets finds a secret. You should be able to test it by staging a file with any high-entropy string like `"bblfwk3u4bt484+afw4avev5ae+afr4?/fa"` (it also has other ways to detect secrets, this is just the most straightforward to test). - -You can permit exceptions by adding an inline comment containing `pragma: allowlist secret` - -The command that is actually run by the pre-commit hook is: `git diff --staged --name-only -z | xargs -0 detect-secrets-hook --baseline .secrets.baseline` - -You can also run against all tracked files staged or not: `git ls-files -z | xargs -0 detect-secrets-hook --baseline .secrets.baseline` - -### Postgres - -Local postgres implementation is handled by [docker compose](https://github.com/18F/notifications-api/blob/main/docker-compose.devcontainer.yml) - -### Redis - -Local redis implementation is handled by [docker compose](https://github.com/18F/notifications-api/blob/main/docker-compose.devcontainer.yml) - -## To test the application - -``` -# install dependencies, etc. -make bootstrap - -make test -``` - -## To run a local OWASP scan - -1. Run `make run-flask` from within the dev container. -2. On your host machine run: - -``` -docker run -v $(pwd):/zap/wrk/:rw --network="notify-network" -t owasp/zap2docker-weekly zap-api-scan.py -t http://dev:6011/_status -f openapi -c zap.conf -``` - -## To run scheduled tasks - -``` -# After scheduling some tasks, open a third terminal in your running devcontainer and run celery beat -make run-celery-beat -``` - -## To run one off tasks (Ignore for Quick Start) - -Tasks are run through the `flask` command - run `flask --help` for more information. There are two sections we need to -care about: `flask db` contains alembic migration commands, and `flask command` contains all of our custom commands. For -example, to purge all dynamically generated functional test data, do the following: - -Local (from inside the devcontainer) - -``` -flask command purge_functional_test_data -u -``` - -Remote - -``` -cf run-task notify-api "flask command purge_functional_test_data -u " -``` - -All commands and command options have a --help command if you need more information. - -## Further documentation [DEPRECATED] +## UK docs that may still be helpful - [Writing public APIs](docs/writing-public-apis.md) - [Updating dependencies](https://github.com/alphagov/notifications-manuals/wiki/Dependencies) diff --git a/docs/database-management.md b/docs/database-management.md new file mode 100644 index 000000000..589df97fd --- /dev/null +++ b/docs/database-management.md @@ -0,0 +1,55 @@ +# Database management + +## Initial state + +In Notify, several aspects of the system are loaded into the database via migration. This means that +application setup requires loading and overwriting historical data in order to arrive at the current +configuration. + +[Here are notes](https://docs.google.com/document/d/1ZgiUtJFvRBKBxB1ehiry2Dup0Q5iIwbdCU5spuqUFTo/edit#) +about what is loaded into which tables, and some plans for how we might manage that in the future. + +Flask does not seem to have a great way to squash migrations, but rather wants you to recreate them +from the DB structure. This means it's easy to recreate the tables, but hard to recreate the initial data. + +## Migrations + +Create a migration: + +``` +flask db migrate +``` + +Trim any auto-generated stuff down to what you want, and manually rename it to be in numerical order. +We should only have one migration branch. + +Running migrations locally: + +``` +flask db upgrade +``` + +This should happen automatically on cloud.gov, but if you need to run a one-off migration for some reason: + +``` +cf run-task notifications-api-staging --commmand "flask db upgrade" --name db-upgrade +``` + +## Purging user data + +There is a Flask command to wipe user-created data (users, services, etc.). + +The command should stop itself if it's run in a production environment, but, you know, please don't run it +in a production environment. + +Running locally: + +``` +flask command purge_functional_test_data -u +``` + +Running on cloud.gov: + +``` +cf run-task notify-api "flask command purge_functional_test_data -u " +``` diff --git a/docs/infra-onboarding.md b/docs/infra-onboarding.md new file mode 100644 index 000000000..6d7789dfe --- /dev/null +++ b/docs/infra-onboarding.md @@ -0,0 +1,7 @@ +# Infrastructure onboarding + +- [ ] Join [the GSA GitHub org](https://github.com/GSA/GitHub-Administration#join-the-gsa-organization) +- [ ] Get permissions for the repos +- [ ] Get access to the cloud.gov org && space +- [ ] Get access to AWS, if necessary +- [ ] Pull down creds from cloud.gov and create the local .env file \ No newline at end of file diff --git a/docs/infra-setup.md b/docs/infra-setup.md new file mode 100644 index 000000000..9bd83f058 --- /dev/null +++ b/docs/infra-setup.md @@ -0,0 +1,20 @@ +# Setting up the infrastructure + +## Steps to prepare SES + +1. Go to SES console for \$AWS_REGION and create new origin and destination emails. AWS will send a verification via email which you'll need to complete. +2. Find and replace instances in the repo of "testsender", "testreceiver" and "dispostable.com", with your origin and destination email addresses, which you verified in step 1 above. + +TODO: create env vars for these origin and destination email addresses for the root service, and create new migrations to update postgres seed fixtures + +## Steps to prepare SNS + +1. Go to Pinpoints console for \$AWS_PINPOINT_REGION and choose "create new project", then "configure for sms" +2. Tick the box at the top to enable SMS, choose "transactional" as the default type and save +3. In the lefthand sidebar, go the "SMS and Voice" (bottom) and choose "Phone Numbers" +4. Under "Number Settings" choose "Request Phone Number" +5. Choose Toll-free number, tick SMS, untick Voice, choose "transactional", hit next and then "request" +6. Go to SNS console for \$AWS_PINPOINT_REGION, look at lefthand sidebar under "Mobile" and go to "Text Messaging (SMS)" +7. Scroll down to "Sandbox destination phone numbers" and tap "Add phone number" then follow the steps to verify (you'll need to be able to retrieve a code sent to each number) + +At this point, you _should_ be able to complete both the email and phone verification steps of the Notify user sign up process! 🎉 \ No newline at end of file diff --git a/docs/one-off-tasks.md b/docs/one-off-tasks.md new file mode 100644 index 000000000..edbfcefea --- /dev/null +++ b/docs/one-off-tasks.md @@ -0,0 +1,22 @@ +# One-off tasks + +For these, we're using Flask commands, which live in [`/app/commands.py`](../app/commands.py). + +This includes things that might be one-time operations! Using a command allows the operation to be tested, +both with `pytest` and with trial runs. + +To run a command on cloud.gov, use this format: + +``` +cf run-task CLOUD-GOV-SPACE --commmand "YOUR COMMAND HERE" --name YOUR-COMMAND +``` + +[Here's more documentation](https://docs.cloudfoundry.org/devguide/using-tasks.html) about Cloud Foundry tasks. + +## Celery scheduled tasks + +After scheduling some tasks, run celery beat to get them moving: + +``` +make run-celery-beat +``` diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 000000000..2294c52cf --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,31 @@ +# Testing + +``` +# install dependencies, etc. +make bootstrap + +make test +``` + +This will run: +- flake8 for code styling +- isort for import styling +- pytest for the test suite + +On GitHub, in addition to these tests, we run: +- bandit for code security +- pip-audit for dependency vulnerabilities +- OWASP for dynamic scanning + +## CI testing + +We're using GitHub Actions. See [/.github](../.github/) for the configuration. + +## To run a local OWASP scan + +1. Run `make run-flask` from within the dev container. +2. On your host machine run: + +``` +docker run -v $(pwd):/zap/wrk/:rw --network="notify-network" -t owasp/zap2docker-weekly zap-api-scan.py -t http://dev:6011/_status -f openapi -c zap.conf +``` \ No newline at end of file From 5f4d8ee3effc9ab82639670eb952c7fa7741f31b Mon Sep 17 00:00:00 2001 From: stvnrlly Date: Thu, 20 Oct 2022 14:05:54 -0400 Subject: [PATCH 06/19] put instructions directly in .env file --- sample.env | 76 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/sample.env b/sample.env index 78761beac..d8694b5c1 100644 --- a/sample.env +++ b/sample.env @@ -1,5 +1,47 @@ +# 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 +# +# 4. Replace `NOTIFY_EMAIL_DOMAIN` with the domain your emails will come from (i.e. the "origination email" in your SES project) +# +# 5. Replace `SECRET_KEY` and `DANGEROUS_SALT` with high-entropy secret values +# + # ## 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" +AWS_PINPOINT_REGION=us-west-2 +AWS_US_TOLL_FREE_NUMBER=+18446120782 + +############################################################# + +# Local Docker setup, all overwritten in cloud.gov +ADMIN_BASE_URL=http://admin:6012 +API_HOST_NAME=http://dev:6011 +REDIS_URL=redis://redis:6380 +REDIS_ENABLED=1 +SQLALCHEMY_DATABASE_URI=postgresql://postgres:chummy@db:5432/notification_api +SQLALCHEMY_DATABASE_TEST_URI=postgresql://postgres:chummy@db:5432/test_notification_api + +# Local direct setup, all overwritten in cloud.gov +# ADMIN_BASE_URL=http://localhost:6012 +# API_HOST_NAME=http://localhost:6011 +# REDIS_URL=redis://localhost:6379 +# REDIS_ENABLED=1 +# SQLALCHEMY_DATABASE_URI=postgresql://localhost:5432/notification_api +# SQLALCHEMY_DATABASE_TEST_URI=postgresql://localhost:5432/test_notification_api + +############################################################# + # Debug DEBUG=True ANTIVIRUS_ENABLED=0 @@ -10,10 +52,7 @@ NOTIFY_APP_NAME=api NOTIFY_EMAIL_DOMAIN=dispostable.com NOTIFY_LOG_PATH=/workspace/logs/app.log -# secrets that internal apps, such as the admin app or document download, must use to authenticate with the API -ADMIN_CLIENT_ID=notify-admin -ADMIN_CLIENT_SECRET=dev-notify-secret-key -GOVUK_ALERTS_CLIENT_ID=govuk-alerts +############################################################# # Flask FLASK_APP=application.py @@ -22,28 +61,7 @@ WERKZEUG_DEBUG_PIN=off SECRET_KEY=dev-notify-secret-key DANGEROUS_SALT=dev-notify-salt -# URL of admin app, this is overriden on cloudfoundry -ADMIN_BASE_URL=http://admin:6012 - -# URL of api app, this is overriden on cloudfoundry -API_HOST_NAME=http://dev:6011 - -# URL of redis instance, this is overriden on cloudfoundry -REDIS_URL=redis://redis:6380 -REDIS_ENABLED=1 - -# DB connection string for local docker, overriden on remote with vcap env vars -SQLALCHEMY_DATABASE_URI=postgresql://postgres:chummy@db:5432/notification_api - -# For testing in local docker -SQLALCHEMY_DATABASE_TEST_URI=postgresql://postgres:chummy@db:5432/test_notification_api - -# DB connection string for local non-docker connection -# SQLALCHEMY_DATABASE_URI=postgresql://user:password@localhost:5432/notification_api - -# 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" -AWS_PINPOINT_REGION=us-west-2 -AWS_US_TOLL_FREE_NUMBER=+18446120782 +# secrets that internal apps, such as the admin app or document download, must use to authenticate with the API +ADMIN_CLIENT_ID=notify-admin +ADMIN_CLIENT_SECRET=dev-notify-secret-key +GOVUK_ALERTS_CLIENT_ID=govuk-alerts From 3e5e2f5017d6bd16b7240f06b9899a4038f1531e Mon Sep 17 00:00:00 2001 From: stvnrlly Date: Thu, 20 Oct 2022 14:06:16 -0400 Subject: [PATCH 07/19] create pipenv files --- Pipfile | 80 ++ Pipfile.lock | 1903 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.in | 4 +- 3 files changed, 1985 insertions(+), 2 deletions(-) create mode 100644 Pipfile create mode 100644 Pipfile.lock diff --git a/Pipfile b/Pipfile new file mode 100644 index 000000000..c1ae3089f --- /dev/null +++ b/Pipfile @@ -0,0 +1,80 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +alembic = "==1.7.7" +amqp = "==5.1.1" +arrow = "==1.2.2" +asn1crypto = "==1.5.1" +async-timeout = "==4.0.2" +attrs = "==21.4.0" +awscli = "==1.24.8" +bcrypt = "==3.2.2" +beautifulsoup4 = "==4.11.1" +billiard = "==3.6.4.0" +bleach = "==4.1.0" +blinker = "==1.4" +boto3 = "==1.23.8" +botocore = "==1.26.8" +cachetools = "==5.1.0" +celery = {version = "==5.2.7", extras = ["redis"]} +certifi = "==2022.5.18.1" +cffi = "==1.15.0" +charset-normalizer = "==2.0.12" +click = "==8.1.3" +click-datetime = "==0.2" +click-didyoumean = "==0.3.0" +click-plugins = "==1.1.1" +click-repl = "==0.2.0" +colorama = "==0.4.4" +defusedxml = "==0.7.1" +deprecated = "==1.2.13" +dnspython = "==2.2.1" +docopt = "==0.6.2" +docutils = "==0.16" +eventlet = "==0.33.1" +flask = "~=2.1.2" +flask-bcrypt = "==1.0.1" +flask-marshmallow = "==0.14.0" +flask-migrate = "==3.1.0" +flask-redis = "==0.4.0" +flask-sqlalchemy = {version = "==2.5.1", ref = "aa7a61a5357cf6f5dcc135d98c781192457aa6fa", git = "https://github.com/pallets-eco/flask-sqlalchemy.git"} +gunicorn = {version = "==20.1.0", extras = ["eventlet"], ref = "1299ea9e967a61ae2edebe191082fd169b864c64", git = "https://github.com/benoitc/gunicorn.git"} +iso8601 = "==1.0.2" +itsdangerous = "==2.1.2" +jsonschema = {version = "==4.5.1", extras = ["format"]} +lxml = "==4.9.1" +marshmallow = "==3.15.0" +marshmallow-sqlalchemy = "==0.28.1" +notifications-python-client = "==6.3.0" +notifications-utils = {git = "https://github.com/GSA/notifications-utils.git"} +oscrypto = "==1.3.0" +psycopg2-binary = "==2.9.3" +pyjwt = "==2.4.0" +python-dotenv = "==0.20.0" +sqlalchemy = "==1.4.40" +werkzeug = "~=2.1.1" +# PaaS packages +awscli-cwlogs = "==1.4.6" +# gds metrics packages +prometheus-client = "==0.14.1" +gds-metrics = {ref = "6f1840a57b6fb1ee40b7e84f2f18ec229de8aa72", git = "https://github.com/alphagov/gds_metrics_python.git"} + +[dev-packages] +flake8 = "==4.0.1" +flake8-bugbear = "==22.4.25" +isort = "==5.10.1" +moto = "==3.1.9" +pytest = "==7.1.2" +pytest-env = "==0.6.2" +pytest-mock = "==3.7.0" +pytest-cov = "==3.0.0" +pytest-xdist = "==2.5.0" +freezegun = "==1.2.1" +requests-mock = "==1.9.3" +jinja2-cli = {version = "==0.8.2", extras = ["yaml"]} + +[requires] +python_version = "3.9" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 000000000..8cd0bf67e --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,1903 @@ +{ + "_meta": { + "hash": { + "sha256": "eca9a39871c8db3e82fb384c39afc89fdc3c145c87653f5090927e578618ce11" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.9" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "alembic": { + "hashes": [ + "sha256:29be0856ec7591c39f4e1cb10f198045d890e6e2274cf8da80cb5e721a09642b", + "sha256:4961248173ead7ce8a21efb3de378f13b8398e6630fab0eb258dc74a8af24c58" + ], + "index": "pypi", + "version": "==1.7.7" + }, + "amqp": { + "hashes": [ + "sha256:2c1b13fecc0893e946c65cbd5f36427861cffa4ea2201d8f6fca22e2a373b5e2", + "sha256:6f0956d2c23d8fa6e7691934d8c3930eadb44972cbbd1a7ae3a520f735d43359" + ], + "index": "pypi", + "version": "==5.1.1" + }, + "arrow": { + "hashes": [ + "sha256:05caf1fd3d9a11a1135b2b6f09887421153b94558e5ef4d090b567b47173ac2b", + "sha256:d622c46ca681b5b3e3574fcb60a04e5cc81b9625112d5fb2b44220c36c892177" + ], + "index": "pypi", + "version": "==1.2.2" + }, + "asn1crypto": { + "hashes": [ + "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c", + "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67" + ], + "index": "pypi", + "version": "==1.5.1" + }, + "async-timeout": { + "hashes": [ + "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15", + "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c" + ], + "index": "pypi", + "version": "==4.0.2" + }, + "attrs": { + "hashes": [ + "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", + "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" + ], + "index": "pypi", + "version": "==21.4.0" + }, + "awscli": { + "hashes": [ + "sha256:65d9414ead4f4027232bc889d5b2c8e4f205415ed877e1f9125ea238d89d025e", + "sha256:a895e378ebbf407b1dfc31205918c1e8521948d770e79006dad1316bf4987de0" + ], + "index": "pypi", + "version": "==1.24.8" + }, + "awscli-cwlogs": { + "hashes": [ + "sha256:44d2fe77d109b7b630fb8f6c06760ff6c5ec9861be16413fd5b977f5a4971f83" + ], + "index": "pypi", + "version": "==1.4.6" + }, + "bcrypt": { + "hashes": [ + "sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521", + "sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb", + "sha256:4e029cef560967fb0cf4a802bcf4d562d3d6b4b1bf81de5ec1abbe0f1adb027e", + "sha256:61bae49580dce88095d669226d5076d0b9d927754cedbdf76c6c9f5099ad6f26", + "sha256:6d2cb9d969bfca5bc08e45864137276e4c3d3d7de2b162171def3d188bf9d34a", + "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e", + "sha256:7d9ba2e41e330d2af4af6b1b6ec9e6128e91343d0b4afb9282e54e5508f31baa", + "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129", + "sha256:88273d806ab3a50d06bc6a2fc7c87d737dd669b76ad955f449c43095389bc8fb", + "sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40", + "sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa" + ], + "index": "pypi", + "version": "==3.2.2" + }, + "beautifulsoup4": { + "hashes": [ + "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30", + "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693" + ], + "index": "pypi", + "version": "==4.11.1" + }, + "billiard": { + "hashes": [ + "sha256:299de5a8da28a783d51b197d496bef4f1595dd023a93a4f59dde1886ae905547", + "sha256:87103ea78fa6ab4d5c751c4909bcff74617d985de7fa8b672cf8618afd5a875b" + ], + "index": "pypi", + "version": "==3.6.4.0" + }, + "bleach": { + "hashes": [ + "sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da", + "sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994" + ], + "index": "pypi", + "version": "==4.1.0" + }, + "blinker": { + "hashes": [ + "sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6" + ], + "index": "pypi", + "version": "==1.4" + }, + "boto3": { + "hashes": [ + "sha256:15733c2bbedce7a36fcf1749560c72c3ee90785aa6302a98658c7bffdcbe1f2a", + "sha256:ea8ebcea4ccb70d1cf57526d9eec6012c76796f28ada3e9cc1d89178683d8107" + ], + "index": "pypi", + "version": "==1.23.8" + }, + "botocore": { + "hashes": [ + "sha256:620851daf1245af5bc28137aa821375bac964aa0eddc482437c783fe01e298fc", + "sha256:e786722cb14de7319331cc55e9092174de66a768559700ef656d05ff41b3e24f" + ], + "index": "pypi", + "version": "==1.26.8" + }, + "cachetools": { + "hashes": [ + "sha256:4ebbd38701cdfd3603d1f751d851ed248ab4570929f2d8a7ce69e30c420b141c", + "sha256:8b3b8fa53f564762e5b221e9896798951e7f915513abf2ba072ce0f07f3f5a98" + ], + "index": "pypi", + "version": "==5.1.0" + }, + "celery": { + "extras": [ + "redis" + ], + "hashes": [ + "sha256:138420c020cd58d6707e6257b6beda91fd39af7afde5d36c6334d175302c0e14", + "sha256:fafbd82934d30f8a004f81e8f7a062e31413a23d444be8ee3326553915958c6d" + ], + "index": "pypi", + "version": "==5.2.7" + }, + "certifi": { + "hashes": [ + "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7", + "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a" + ], + "index": "pypi", + "version": "==2022.5.18.1" + }, + "cffi": { + "hashes": [ + "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3", + "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2", + "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636", + "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20", + "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728", + "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27", + "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66", + "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443", + "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0", + "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7", + "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39", + "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605", + "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a", + "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37", + "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029", + "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139", + "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc", + "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df", + "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14", + "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880", + "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2", + "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a", + "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e", + "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474", + "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024", + "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8", + "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0", + "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e", + "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a", + "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e", + "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032", + "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6", + "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e", + "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b", + "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e", + "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954", + "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962", + "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c", + "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4", + "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55", + "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962", + "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023", + "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c", + "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6", + "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8", + "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382", + "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7", + "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc", + "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997", + "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796" + ], + "index": "pypi", + "version": "==1.15.0" + }, + "charset-normalizer": { + "hashes": [ + "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", + "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df" + ], + "index": "pypi", + "version": "==2.0.12" + }, + "click": { + "hashes": [ + "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", + "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" + ], + "index": "pypi", + "version": "==8.1.3" + }, + "click-datetime": { + "hashes": [ + "sha256:7256ca518e648ada8e2550239ab328de125906e5b7199a5bd5bcbb4dfe28f946", + "sha256:c562ad24b3711784a655a49141b4a87933a78608fe66296259acae95fda5e115" + ], + "index": "pypi", + "version": "==0.2" + }, + "click-didyoumean": { + "hashes": [ + "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667", + "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035" + ], + "index": "pypi", + "version": "==0.3.0" + }, + "click-plugins": { + "hashes": [ + "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b", + "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8" + ], + "index": "pypi", + "version": "==1.1.1" + }, + "click-repl": { + "hashes": [ + "sha256:94b3fbbc9406a236f176e0506524b2937e4b23b6f4c0c0b2a0a83f8a64e9194b", + "sha256:cd12f68d745bf6151210790540b4cb064c7b13e571bc64b6957d98d120dacfd8" + ], + "index": "pypi", + "version": "==0.2.0" + }, + "colorama": { + "hashes": [ + "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", + "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2" + ], + "index": "pypi", + "version": "==0.4.4" + }, + "defusedxml": { + "hashes": [ + "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", + "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61" + ], + "index": "pypi", + "version": "==0.7.1" + }, + "deprecated": { + "hashes": [ + "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d", + "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d" + ], + "index": "pypi", + "version": "==1.2.13" + }, + "dnspython": { + "hashes": [ + "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e", + "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f" + ], + "index": "pypi", + "version": "==2.2.1" + }, + "docopt": { + "hashes": [ + "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491" + ], + "index": "pypi", + "version": "==0.6.2" + }, + "docutils": { + "hashes": [ + "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", + "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc" + ], + "index": "pypi", + "version": "==0.16" + }, + "eventlet": { + "hashes": [ + "sha256:a085922698e5029f820cf311a648ac324d73cec0e4792877609d978a4b5bbf31", + "sha256:afbe17f06a58491e9aebd7a4a03e70b0b63fd4cf76d8307bae07f280479b1515" + ], + "index": "pypi", + "version": "==0.33.1" + }, + "flask": { + "hashes": [ + "sha256:15972e5017df0575c3d6c090ba168b6db90259e620ac8d7ea813a396bad5b6cb", + "sha256:9013281a7402ad527f8fd56375164f3aa021ecfaff89bfe3825346c24f87e04c" + ], + "index": "pypi", + "version": "==2.1.3" + }, + "flask-bcrypt": { + "hashes": [ + "sha256:062fd991dc9118d05ac0583675507b9fe4670e44416c97e0e6819d03d01f808a", + "sha256:f07b66b811417ea64eb188ae6455b0b708a793d966e1a80ceec4a23bc42a4369" + ], + "index": "pypi", + "version": "==1.0.1" + }, + "flask-marshmallow": { + "hashes": [ + "sha256:2adcd782b5a4a6c5ae3c96701f320d8ca6997995a52b2661093c56cc3ed24754", + "sha256:bd01a6372cbe50e36f205cfff0fc5dab0b7b662c4c8b2c4fc06a3151b2950950" + ], + "index": "pypi", + "version": "==0.14.0" + }, + "flask-migrate": { + "hashes": [ + "sha256:57d6060839e3a7f150eaab6fe4e726d9e3e7cffe2150fb223d73f92421c6d1d9", + "sha256:a6498706241aba6be7a251078de9cf166d74307bca41a4ca3e403c9d39e2f897" + ], + "index": "pypi", + "version": "==3.1.0" + }, + "flask-redis": { + "hashes": [ + "sha256:8d79eef4eb1217095edab603acc52f935b983ae4b7655ee7c82c0dfd87315d17", + "sha256:e1fccc11e7ea35c2a4d68c0b9aa58226a098e45e834d615c7b6c4928b01ddd6c" + ], + "index": "pypi", + "version": "==0.4.0" + }, + "flask-sqlalchemy": { + "hashes": [ + "sha256:2bda44b43e7cacb15d4e05ff3cc1f8bc97936cc464623424102bfc2c35e95912", + "sha256:f12c3d4cc5cc7fdcc148b9527ea05671718c3ea45d50c7e732cceb33f574b390" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.5.1" + }, + "flask-sqlalchemy==2-5-1": { + "git": "https://github.com/pallets-eco/flask-sqlalchemy.git", + "ref": "aa7a61a5357cf6f5dcc135d98c781192457aa6fa" + }, + "flask-sqlalchemy==2.5.1": { + "git": "https://github.com/pallets-eco/flask-sqlalchemy.git", + "ref": "aa7a61a5357cf6f5dcc135d98c781192457aa6fa" + }, + "fqdn": { + "hashes": [ + "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", + "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014" + ], + "version": "==1.5.1" + }, + "gds-metrics": { + "git": "https://github.com/alphagov/gds_metrics_python.git", + "ref": "6f1840a57b6fb1ee40b7e84f2f18ec229de8aa72" + }, + "geojson": { + "hashes": [ + "sha256:6e4bb7ace4226a45d9c8c8b1348b3fc43540658359f93c3f7e03efa9f15f658a", + "sha256:ccbd13368dd728f4e4f13ffe6aaf725b6e802c692ba0dde628be475040c534ba" + ], + "version": "==2.5.0" + }, + "govuk-bank-holidays": { + "hashes": [ + "sha256:6c993f59bff512740066ae5891a9ce3155e94291560d1d5169fea6e634111a46", + "sha256:9e214828802bbed303f01dacee3facc2296a8bcac9c814654ac7300b37381f78" + ], + "version": "==0.11" + }, + "greenlet": { + "hashes": [ + "sha256:0120a879aa2b1ac5118bce959ea2492ba18783f65ea15821680a256dfad04754", + "sha256:025b8de2273d2809f027d347aa2541651d2e15d593bbce0d5f502ca438c54136", + "sha256:05ae7383f968bba4211b1fbfc90158f8e3da86804878442b4fb6c16ccbcaa519", + "sha256:0914f02fcaa8f84f13b2df4a81645d9e82de21ed95633765dd5cc4d3af9d7403", + "sha256:0971d37ae0eaf42344e8610d340aa0ad3d06cd2eee381891a10fe771879791f9", + "sha256:0a954002064ee919b444b19c1185e8cce307a1f20600f47d6f4b6d336972c809", + "sha256:0aa1845944e62f358d63fcc911ad3b415f585612946b8edc824825929b40e59e", + "sha256:104f29dd822be678ef6b16bf0035dcd43206a8a48668a6cae4d2fe9c7a7abdeb", + "sha256:11fc7692d95cc7a6a8447bb160d98671ab291e0a8ea90572d582d57361360f05", + "sha256:17a69967561269b691747e7f436d75a4def47e5efcbc3c573180fc828e176d80", + "sha256:2794eef1b04b5ba8948c72cc606aab62ac4b0c538b14806d9c0d88afd0576d6b", + "sha256:2c6e942ca9835c0b97814d14f78da453241837419e0d26f7403058e8db3e38f8", + "sha256:2ccdc818cc106cc238ff7eba0d71b9c77be868fdca31d6c3b1347a54c9b187b2", + "sha256:325f272eb997916b4a3fc1fea7313a8adb760934c2140ce13a2117e1b0a8095d", + "sha256:39464518a2abe9c505a727af7c0b4efff2cf242aa168be5f0daa47649f4d7ca8", + "sha256:3a24f3213579dc8459e485e333330a921f579543a5214dbc935bc0763474ece3", + "sha256:3aeac044c324c1a4027dca0cde550bd83a0c0fbff7ef2c98df9e718a5086c194", + "sha256:3c22998bfef3fcc1b15694818fc9b1b87c6cc8398198b96b6d355a7bcb8c934e", + "sha256:467b73ce5dcd89e381292fb4314aede9b12906c18fab903f995b86034d96d5c8", + "sha256:4a8b58232f5b72973350c2b917ea3df0bebd07c3c82a0a0e34775fc2c1f857e9", + "sha256:4f74aa0092602da2069df0bc6553919a15169d77bcdab52a21f8c5242898f519", + "sha256:5662492df0588a51d5690f6578f3bbbd803e7f8d99a99f3bf6128a401be9c269", + "sha256:5c2d21c2b768d8c86ad935e404cc78c30d53dea009609c3ef3a9d49970c864b5", + "sha256:5edf75e7fcfa9725064ae0d8407c849456553a181ebefedb7606bac19aa1478b", + "sha256:60839ab4ea7de6139a3be35b77e22e0398c270020050458b3d25db4c7c394df5", + "sha256:62723e7eb85fa52e536e516ee2ac91433c7bb60d51099293671815ff49ed1c21", + "sha256:64e10f303ea354500c927da5b59c3802196a07468332d292aef9ddaca08d03dd", + "sha256:66aa4e9a726b70bcbfcc446b7ba89c8cec40f405e51422c39f42dfa206a96a05", + "sha256:695d0d8b5ae42c800f1763c9fce9d7b94ae3b878919379150ee5ba458a460d57", + "sha256:70048d7b2c07c5eadf8393e6398595591df5f59a2f26abc2f81abca09610492f", + "sha256:7afa706510ab079fd6d039cc6e369d4535a48e202d042c32e2097f030a16450f", + "sha256:7cf37343e43404699d58808e51f347f57efd3010cc7cee134cdb9141bd1ad9ea", + "sha256:8149a6865b14c33be7ae760bcdb73548bb01e8e47ae15e013bf7ef9290ca309a", + "sha256:814f26b864ed2230d3a7efe0336f5766ad012f94aad6ba43a7c54ca88dd77cba", + "sha256:82a38d7d2077128a017094aff334e67e26194f46bd709f9dcdacbf3835d47ef5", + "sha256:83a7a6560df073ec9de2b7cb685b199dfd12519bc0020c62db9d1bb522f989fa", + "sha256:8415239c68b2ec9de10a5adf1130ee9cb0ebd3e19573c55ba160ff0ca809e012", + "sha256:88720794390002b0c8fa29e9602b395093a9a766b229a847e8d88349e418b28a", + "sha256:890f633dc8cb307761ec566bc0b4e350a93ddd77dc172839be122be12bae3e10", + "sha256:8926a78192b8b73c936f3e87929931455a6a6c6c385448a07b9f7d1072c19ff3", + "sha256:8c0581077cf2734569f3e500fab09c0ff6a2ab99b1afcacbad09b3c2843ae743", + "sha256:8fda1139d87ce5f7bd80e80e54f9f2c6fe2f47983f1a6f128c47bf310197deb6", + "sha256:91a84faf718e6f8b888ca63d0b2d6d185c8e2a198d2a7322d75c303e7097c8b7", + "sha256:924df1e7e5db27d19b1359dc7d052a917529c95ba5b8b62f4af611176da7c8ad", + "sha256:949c9061b8c6d3e6e439466a9be1e787208dec6246f4ec5fffe9677b4c19fcc3", + "sha256:9649891ab4153f217f319914455ccf0b86986b55fc0573ce803eb998ad7d6854", + "sha256:96656c5f7c95fc02c36d4f6ef32f4e94bb0b6b36e6a002c21c39785a4eec5f5d", + "sha256:a812df7282a8fc717eafd487fccc5ba40ea83bb5b13eb3c90c446d88dbdfd2be", + "sha256:a8d24eb5cb67996fb84633fdc96dbc04f2d8b12bfcb20ab3222d6be271616b67", + "sha256:bef49c07fcb411c942da6ee7d7ea37430f830c482bf6e4b72d92fd506dd3a427", + "sha256:bffba15cff4802ff493d6edcf20d7f94ab1c2aee7cfc1e1c7627c05f1102eee8", + "sha256:c0643250dd0756f4960633f5359884f609a234d4066686754e834073d84e9b51", + "sha256:c6f90234e4438062d6d09f7d667f79edcc7c5e354ba3a145ff98176f974b8132", + "sha256:c8c9301e3274276d3d20ab6335aa7c5d9e5da2009cccb01127bddb5c951f8870", + "sha256:c8ece5d1a99a2adcb38f69af2f07d96fb615415d32820108cd340361f590d128", + "sha256:cb863057bed786f6622982fb8b2c122c68e6e9eddccaa9fa98fd937e45ee6c4f", + "sha256:ccbe7129a282ec5797df0451ca1802f11578be018a32979131065565da89b392", + "sha256:d25cdedd72aa2271b984af54294e9527306966ec18963fd032cc851a725ddc1b", + "sha256:d75afcbb214d429dacdf75e03a1d6d6c5bd1fa9c35e360df8ea5b6270fb2211c", + "sha256:d7815e1519a8361c5ea2a7a5864945906f8e386fa1bc26797b4d443ab11a4589", + "sha256:eb6ac495dccb1520667cfea50d89e26f9ffb49fa28496dea2b95720d8b45eb54", + "sha256:ec615d2912b9ad807afd3be80bf32711c0ff9c2b00aa004a45fd5d5dde7853d9", + "sha256:f5e09dc5c6e1796969fd4b775ea1417d70e49a5df29aaa8e5d10675d9e11872c", + "sha256:f6661b58412879a2aa099abb26d3c93e91dedaba55a6394d1fb1512a77e85de9", + "sha256:f7d20c3267385236b4ce54575cc8e9f43e7673fc761b069c820097092e318e3b", + "sha256:fe7c51f8a2ab616cb34bc33d810c887e89117771028e1e3d3b77ca25ddeace04" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.1.3.post0" + }, + "gunicorn[eventlet]==20-1-0": { + "git": "https://github.com/benoitc/gunicorn.git", + "ref": "1299ea9e967a61ae2edebe191082fd169b864c64" + }, + "gunicorn[eventlet]==20.1.0": { + "git": "https://github.com/benoitc/gunicorn.git", + "ref": "1299ea9e967a61ae2edebe191082fd169b864c64" + }, + "idna": { + "hashes": [ + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + ], + "version": "==3.4" + }, + "importlib-metadata": { + "hashes": [ + "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab", + "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43" + ], + "markers": "python_version < '3.10'", + "version": "==5.0.0" + }, + "iso8601": { + "hashes": [ + "sha256:27f503220e6845d9db954fb212b95b0362d8b7e6c1b2326a87061c3de93594b1", + "sha256:d7bc01b1c2a43b259570bb307f057abc578786ea734ba2b87b836c5efc5bd443" + ], + "index": "pypi", + "version": "==1.0.2" + }, + "isoduration": { + "hashes": [ + "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", + "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042" + ], + "version": "==20.11.0" + }, + "itsdangerous": { + "hashes": [ + "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", + "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" + ], + "index": "pypi", + "version": "==2.1.2" + }, + "jinja2": { + "hashes": [ + "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", + "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" + ], + "markers": "python_version >= '3.7'", + "version": "==3.1.2" + }, + "jmespath": { + "hashes": [ + "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", + "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe" + ], + "markers": "python_version >= '3.7'", + "version": "==1.0.1" + }, + "jsonpointer": { + "hashes": [ + "sha256:51801e558539b4e9cd268638c078c6c5746c9ac96bc38152d443400e4f3793e9", + "sha256:97cba51526c829282218feb99dab1b1e6bdf8efd1c43dc9d57be093c0d69c99a" + ], + "version": "==2.3" + }, + "jsonschema": { + "extras": [ + "format" + ], + "hashes": [ + "sha256:71b5e39324422543546572954ce71c67728922c104902cb7ce252e522235b33f", + "sha256:7c6d882619340c3347a1bf7315e147e6d3dae439033ae6383d6acb908c101dfc" + ], + "index": "pypi", + "version": "==4.5.1" + }, + "kombu": { + "hashes": [ + "sha256:37cee3ee725f94ea8bb173eaab7c1760203ea53bbebae226328600f9d2799610", + "sha256:8b213b24293d3417bcf0d2f5537b7f756079e3ea232a8386dcc89a59fd2361a4" + ], + "markers": "python_version >= '3.7'", + "version": "==5.2.4" + }, + "lxml": { + "hashes": [ + "sha256:04da965dfebb5dac2619cb90fcf93efdb35b3c6994fea58a157a834f2f94b318", + "sha256:0538747a9d7827ce3e16a8fdd201a99e661c7dee3c96c885d8ecba3c35d1032c", + "sha256:0645e934e940107e2fdbe7c5b6fb8ec6232444260752598bc4d09511bd056c0b", + "sha256:079b68f197c796e42aa80b1f739f058dcee796dc725cc9a1be0cdb08fc45b000", + "sha256:0f3f0059891d3254c7b5fb935330d6db38d6519ecd238ca4fce93c234b4a0f73", + "sha256:10d2017f9150248563bb579cd0d07c61c58da85c922b780060dcc9a3aa9f432d", + "sha256:1355755b62c28950f9ce123c7a41460ed9743c699905cbe664a5bcc5c9c7c7fb", + "sha256:13c90064b224e10c14dcdf8086688d3f0e612db53766e7478d7754703295c7c8", + "sha256:1423631e3d51008871299525b541413c9b6c6423593e89f9c4cfbe8460afc0a2", + "sha256:1436cf0063bba7888e43f1ba8d58824f085410ea2025befe81150aceb123e345", + "sha256:1a7c59c6ffd6ef5db362b798f350e24ab2cfa5700d53ac6681918f314a4d3b94", + "sha256:1e1cf47774373777936c5aabad489fef7b1c087dcd1f426b621fda9dcc12994e", + "sha256:206a51077773c6c5d2ce1991327cda719063a47adc02bd703c56a662cdb6c58b", + "sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc", + "sha256:27e590352c76156f50f538dbcebd1925317a0f70540f7dc8c97d2931c595783a", + "sha256:287605bede6bd36e930577c5925fcea17cb30453d96a7b4c63c14a257118dbb9", + "sha256:2aaf6a0a6465d39b5ca69688fce82d20088c1838534982996ec46633dc7ad6cc", + "sha256:32a73c53783becdb7eaf75a2a1525ea8e49379fb7248c3eeefb9412123536387", + "sha256:41fb58868b816c202e8881fd0f179a4644ce6e7cbbb248ef0283a34b73ec73bb", + "sha256:4780677767dd52b99f0af1f123bc2c22873d30b474aa0e2fc3fe5e02217687c7", + "sha256:4878e667ebabe9b65e785ac8da4d48886fe81193a84bbe49f12acff8f7a383a4", + "sha256:487c8e61d7acc50b8be82bda8c8d21d20e133c3cbf41bd8ad7eb1aaeb3f07c97", + "sha256:4beea0f31491bc086991b97517b9683e5cfb369205dac0148ef685ac12a20a67", + "sha256:4cfbe42c686f33944e12f45a27d25a492cc0e43e1dc1da5d6a87cbcaf2e95627", + "sha256:4d5bae0a37af799207140652a700f21a85946f107a199bcb06720b13a4f1f0b7", + "sha256:4e285b5f2bf321fc0857b491b5028c5f276ec0c873b985d58d7748ece1d770dd", + "sha256:57e4d637258703d14171b54203fd6822fda218c6c2658a7d30816b10995f29f3", + "sha256:5974895115737a74a00b321e339b9c3f45c20275d226398ae79ac008d908bff7", + "sha256:5ef87fca280fb15342726bd5f980f6faf8b84a5287fcc2d4962ea8af88b35130", + "sha256:603a464c2e67d8a546ddaa206d98e3246e5db05594b97db844c2f0a1af37cf5b", + "sha256:6653071f4f9bac46fbc30f3c7838b0e9063ee335908c5d61fb7a4a86c8fd2036", + "sha256:6ca2264f341dd81e41f3fffecec6e446aa2121e0b8d026fb5130e02de1402785", + "sha256:6d279033bf614953c3fc4a0aa9ac33a21e8044ca72d4fa8b9273fe75359d5cca", + "sha256:6d949f53ad4fc7cf02c44d6678e7ff05ec5f5552b235b9e136bd52e9bf730b91", + "sha256:6daa662aba22ef3258934105be2dd9afa5bb45748f4f702a3b39a5bf53a1f4dc", + "sha256:6eafc048ea3f1b3c136c71a86db393be36b5b3d9c87b1c25204e7d397cee9536", + "sha256:830c88747dce8a3e7525defa68afd742b4580df6aa2fdd6f0855481e3994d391", + "sha256:86e92728ef3fc842c50a5cb1d5ba2bc66db7da08a7af53fb3da79e202d1b2cd3", + "sha256:8caf4d16b31961e964c62194ea3e26a0e9561cdf72eecb1781458b67ec83423d", + "sha256:8d1a92d8e90b286d491e5626af53afef2ba04da33e82e30744795c71880eaa21", + "sha256:8f0a4d179c9a941eb80c3a63cdb495e539e064f8054230844dcf2fcb812b71d3", + "sha256:9232b09f5efee6a495a99ae6824881940d6447debe272ea400c02e3b68aad85d", + "sha256:927a9dd016d6033bc12e0bf5dee1dde140235fc8d0d51099353c76081c03dc29", + "sha256:93e414e3206779ef41e5ff2448067213febf260ba747fc65389a3ddaa3fb8715", + "sha256:98cafc618614d72b02185ac583c6f7796202062c41d2eeecdf07820bad3295ed", + "sha256:9c3a88d20e4fe4a2a4a84bf439a5ac9c9aba400b85244c63a1ab7088f85d9d25", + "sha256:9f36de4cd0c262dd9927886cc2305aa3f2210db437aa4fed3fb4940b8bf4592c", + "sha256:a60f90bba4c37962cbf210f0188ecca87daafdf60271f4c6948606e4dabf8785", + "sha256:a614e4afed58c14254e67862456d212c4dcceebab2eaa44d627c2ca04bf86837", + "sha256:ae06c1e4bc60ee076292e582a7512f304abdf6c70db59b56745cca1684f875a4", + "sha256:b122a188cd292c4d2fcd78d04f863b789ef43aa129b233d7c9004de08693728b", + "sha256:b570da8cd0012f4af9fa76a5635cd31f707473e65a5a335b186069d5c7121ff2", + "sha256:bcaa1c495ce623966d9fc8a187da80082334236a2a1c7e141763ffaf7a405067", + "sha256:bd34f6d1810d9354dc7e35158aa6cc33456be7706df4420819af6ed966e85448", + "sha256:be9eb06489bc975c38706902cbc6888f39e946b81383abc2838d186f0e8b6a9d", + "sha256:c4b2e0559b68455c085fb0f6178e9752c4be3bba104d6e881eb5573b399d1eb2", + "sha256:c62e8dd9754b7debda0c5ba59d34509c4688f853588d75b53c3791983faa96fc", + "sha256:c852b1530083a620cb0de5f3cd6826f19862bafeaf77586f1aef326e49d95f0c", + "sha256:d9fc0bf3ff86c17348dfc5d322f627d78273eba545db865c3cd14b3f19e57fa5", + "sha256:dad7b164905d3e534883281c050180afcf1e230c3d4a54e8038aa5cfcf312b84", + "sha256:e5f66bdf0976ec667fc4594d2812a00b07ed14d1b44259d19a41ae3fff99f2b8", + "sha256:e8f0c9d65da595cfe91713bc1222af9ecabd37971762cb830dea2fc3b3bb2acf", + "sha256:edffbe3c510d8f4bf8640e02ca019e48a9b72357318383ca60e3330c23aaffc7", + "sha256:eea5d6443b093e1545ad0210e6cf27f920482bfcf5c77cdc8596aec73523bb7e", + "sha256:ef72013e20dd5ba86a8ae1aed7f56f31d3374189aa8b433e7b12ad182c0d2dfb", + "sha256:f05251bbc2145349b8d0b77c0d4e5f3b228418807b1ee27cefb11f69ed3d233b", + "sha256:f1be258c4d3dc609e654a1dc59d37b17d7fef05df912c01fc2e15eb43a9735f3", + "sha256:f9ced82717c7ec65a67667bb05865ffe38af0e835cdd78728f1209c8fffe0cad", + "sha256:fe17d10b97fdf58155f858606bddb4e037b805a60ae023c009f760d8361a4eb8", + "sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f" + ], + "index": "pypi", + "version": "==4.9.1" + }, + "mako": { + "hashes": [ + "sha256:7fde96466fcfeedb0eed94f187f20b23d85e4cb41444be0e542e2c8c65c396cd", + "sha256:c413a086e38cd885088d5e165305ee8eed04e8b3f8f62df343480da0a385735f" + ], + "markers": "python_version >= '3.7'", + "version": "==1.2.3" + }, + "markupsafe": { + "hashes": [ + "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", + "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", + "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", + "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", + "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", + "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", + "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", + "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", + "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", + "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", + "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", + "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", + "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", + "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", + "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", + "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", + "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", + "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", + "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", + "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", + "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", + "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", + "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", + "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", + "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", + "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", + "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", + "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", + "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", + "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", + "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", + "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", + "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", + "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", + "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", + "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", + "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", + "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", + "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", + "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.1" + }, + "marshmallow": { + "hashes": [ + "sha256:2aaaab4f01ef4f5a011a21319af9fce17ab13bf28a026d1252adab0e035648d5", + "sha256:ff79885ed43b579782f48c251d262e062bce49c65c52412458769a4fb57ac30f" + ], + "index": "pypi", + "version": "==3.15.0" + }, + "marshmallow-sqlalchemy": { + "hashes": [ + "sha256:aa376747296780a56355e3067b9c8bf43a2a1c44ff985de82b3a5d9e161ca2b8", + "sha256:dbb061c19375eca3a7d18358d2ca8bbaee825fc3000a3f114e2698282362b536" + ], + "index": "pypi", + "version": "==0.28.1" + }, + "mistune": { + "hashes": [ + "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e", + "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4" + ], + "version": "==0.8.4" + }, + "notifications-python-client": { + "hashes": [ + "sha256:47c803fcc8b4098d069b92547bb52607b558cec25c19e2697a74faab2e5ef4c0" + ], + "index": "pypi", + "version": "==6.3.0" + }, + "notifications-utils": { + "git": "https://github.com/GSA/notifications-utils.git", + "ref": "90c12da575f4e481452d4fcd2a594204b0c28249" + }, + "orderedset": { + "hashes": [ + "sha256:b2f5ccfb5a86e7b3b3ddf18b29779cc18b24653abf9d6da4bebecf33780a6e29" + ], + "version": "==2.0.3" + }, + "oscrypto": { + "hashes": [ + "sha256:2b2f1d2d42ec152ca90ccb5682f3e051fb55986e1b170ebde472b133713e7085", + "sha256:6f5fef59cb5b3708321db7cca56aed8ad7e662853351e7991fcf60ec606d47a4" + ], + "index": "pypi", + "version": "==1.3.0" + }, + "packaging": { + "hashes": [ + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + ], + "markers": "python_version >= '3.6'", + "version": "==21.3" + }, + "phonenumbers": { + "hashes": [ + "sha256:057d1966962fb86b3dc447bfac2c8e25ceed774509e49b180926a13a99910318", + "sha256:0b234c4a9519fac18d00b3c542f5b429513ea69372d4f95fbd0f716f5e2a89b5" + ], + "version": "==8.12.57" + }, + "prometheus-client": { + "hashes": [ + "sha256:522fded625282822a89e2773452f42df14b5a8e84a86433e3f8a189c1d54dc01", + "sha256:5459c427624961076277fdc6dc50540e2bacb98eebde99886e59ec55ed92093a" + ], + "index": "pypi", + "version": "==0.14.1" + }, + "prompt-toolkit": { + "hashes": [ + "sha256:9696f386133df0fc8ca5af4895afe5d78f5fcfe5258111c2a79a1c3e41ffa96d", + "sha256:9ada952c9d1787f52ff6d5f3484d0b4df8952787c087edf6a1f7c2cb1ea88148" + ], + "markers": "python_full_version >= '3.6.2'", + "version": "==3.0.31" + }, + "psycopg2-binary": { + "hashes": [ + "sha256:01310cf4cf26db9aea5158c217caa92d291f0500051a6469ac52166e1a16f5b7", + "sha256:083a55275f09a62b8ca4902dd11f4b33075b743cf0d360419e2051a8a5d5ff76", + "sha256:090f3348c0ab2cceb6dfbe6bf721ef61262ddf518cd6cc6ecc7d334996d64efa", + "sha256:0a29729145aaaf1ad8bafe663131890e2111f13416b60e460dae0a96af5905c9", + "sha256:0c9d5450c566c80c396b7402895c4369a410cab5a82707b11aee1e624da7d004", + "sha256:10bb90fb4d523a2aa67773d4ff2b833ec00857f5912bafcfd5f5414e45280fb1", + "sha256:12b11322ea00ad8db8c46f18b7dfc47ae215e4df55b46c67a94b4effbaec7094", + "sha256:152f09f57417b831418304c7f30d727dc83a12761627bb826951692cc6491e57", + "sha256:15803fa813ea05bef089fa78835118b5434204f3a17cb9f1e5dbfd0b9deea5af", + "sha256:15c4e4cfa45f5a60599d9cec5f46cd7b1b29d86a6390ec23e8eebaae84e64554", + "sha256:183a517a3a63503f70f808b58bfbf962f23d73b6dccddae5aa56152ef2bcb232", + "sha256:1f14c8b0942714eb3c74e1e71700cbbcb415acbc311c730370e70c578a44a25c", + "sha256:1f6b813106a3abdf7b03640d36e24669234120c72e91d5cbaeb87c5f7c36c65b", + "sha256:280b0bb5cbfe8039205c7981cceb006156a675362a00fe29b16fbc264e242834", + "sha256:2d872e3c9d5d075a2e104540965a1cf898b52274a5923936e5bfddb58c59c7c2", + "sha256:2f2534ab7dc7e776a263b463a16e189eb30e85ec9bbe1bff9e78dae802608932", + "sha256:2f9ffd643bc7349eeb664eba8864d9e01f057880f510e4681ba40a6532f93c71", + "sha256:3303f8807f342641851578ee7ed1f3efc9802d00a6f83c101d21c608cb864460", + "sha256:35168209c9d51b145e459e05c31a9eaeffa9a6b0fd61689b48e07464ffd1a83e", + "sha256:3a79d622f5206d695d7824cbf609a4f5b88ea6d6dab5f7c147fc6d333a8787e4", + "sha256:404224e5fef3b193f892abdbf8961ce20e0b6642886cfe1fe1923f41aaa75c9d", + "sha256:46f0e0a6b5fa5851bbd9ab1bc805eef362d3a230fbdfbc209f4a236d0a7a990d", + "sha256:47133f3f872faf28c1e87d4357220e809dfd3fa7c64295a4a148bcd1e6e34ec9", + "sha256:526ea0378246d9b080148f2d6681229f4b5964543c170dd10bf4faaab6e0d27f", + "sha256:53293533fcbb94c202b7c800a12c873cfe24599656b341f56e71dd2b557be063", + "sha256:539b28661b71da7c0e428692438efbcd048ca21ea81af618d845e06ebfd29478", + "sha256:57804fc02ca3ce0dbfbef35c4b3a4a774da66d66ea20f4bda601294ad2ea6092", + "sha256:63638d875be8c2784cfc952c9ac34e2b50e43f9f0a0660b65e2a87d656b3116c", + "sha256:6472a178e291b59e7f16ab49ec8b4f3bdada0a879c68d3817ff0963e722a82ce", + "sha256:68641a34023d306be959101b345732360fc2ea4938982309b786f7be1b43a4a1", + "sha256:6e82d38390a03da28c7985b394ec3f56873174e2c88130e6966cb1c946508e65", + "sha256:761df5313dc15da1502b21453642d7599d26be88bff659382f8f9747c7ebea4e", + "sha256:7af0dd86ddb2f8af5da57a976d27cd2cd15510518d582b478fbb2292428710b4", + "sha256:7b1e9b80afca7b7a386ef087db614faebbf8839b7f4db5eb107d0f1a53225029", + "sha256:874a52ecab70af13e899f7847b3e074eeb16ebac5615665db33bce8a1009cf33", + "sha256:887dd9aac71765ac0d0bac1d0d4b4f2c99d5f5c1382d8b770404f0f3d0ce8a39", + "sha256:8b344adbb9a862de0c635f4f0425b7958bf5a4b927c8594e6e8d261775796d53", + "sha256:8fc53f9af09426a61db9ba357865c77f26076d48669f2e1bb24d85a22fb52307", + "sha256:91920527dea30175cc02a1099f331aa8c1ba39bf8b7762b7b56cbf54bc5cce42", + "sha256:93cd1967a18aa0edd4b95b1dfd554cf15af657cb606280996d393dadc88c3c35", + "sha256:99485cab9ba0fa9b84f1f9e1fef106f44a46ef6afdeec8885e0b88d0772b49e8", + "sha256:9d29409b625a143649d03d0fd7b57e4b92e0ecad9726ba682244b73be91d2fdb", + "sha256:a29b3ca4ec9defec6d42bf5feb36bb5817ba3c0230dd83b4edf4bf02684cd0ae", + "sha256:a9e1f75f96ea388fbcef36c70640c4efbe4650658f3d6a2967b4cc70e907352e", + "sha256:accfe7e982411da3178ec690baaceaad3c278652998b2c45828aaac66cd8285f", + "sha256:adf20d9a67e0b6393eac162eb81fb10bc9130a80540f4df7e7355c2dd4af9fba", + "sha256:af9813db73395fb1fc211bac696faea4ca9ef53f32dc0cfa27e4e7cf766dcf24", + "sha256:b1c8068513f5b158cf7e29c43a77eb34b407db29aca749d3eb9293ee0d3103ca", + "sha256:b3a24a1982ae56461cc24f6680604fffa2c1b818e9dc55680da038792e004d18", + "sha256:bda845b664bb6c91446ca9609fc69f7db6c334ec5e4adc87571c34e4f47b7ddb", + "sha256:c381bda330ddf2fccbafab789d83ebc6c53db126e4383e73794c74eedce855ef", + "sha256:c3ae8e75eb7160851e59adc77b3a19a976e50622e44fd4fd47b8b18208189d42", + "sha256:d1c1b569ecafe3a69380a94e6ae09a4789bbb23666f3d3a08d06bbd2451f5ef1", + "sha256:def68d7c21984b0f8218e8a15d514f714d96904265164f75f8d3a70f9c295667", + "sha256:dffc08ca91c9ac09008870c9eb77b00a46b3378719584059c034b8945e26b272", + "sha256:e3699852e22aa68c10de06524a3721ade969abf382da95884e6a10ff798f9281", + "sha256:e6aa71ae45f952a2205377773e76f4e3f27951df38e69a4c95440c779e013560", + "sha256:e847774f8ffd5b398a75bc1c18fbb56564cda3d629fe68fd81971fece2d3c67e", + "sha256:ffb7a888a047696e7f8240d649b43fb3644f14f0ee229077e7f6b9f9081635bd" + ], + "index": "pypi", + "version": "==2.9.3" + }, + "pyasn1": { + "hashes": [ + "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", + "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576", + "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf", + "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7", + "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", + "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00", + "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8", + "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86", + "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12", + "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776", + "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", + "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2", + "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3" + ], + "version": "==0.4.8" + }, + "pycparser": { + "hashes": [ + "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", + "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" + ], + "version": "==2.21" + }, + "pyjwt": { + "hashes": [ + "sha256:72d1d253f32dbd4f5c88eaf1fdc62f3a19f676ccbadb9dbc5d07e951b2b26daf", + "sha256:d42908208c699b3b973cbeb01a969ba6a96c821eefb1c5bfe4c390c01d67abba" + ], + "index": "pypi", + "version": "==2.4.0" + }, + "pyparsing": { + "hashes": [ + "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", + "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" + ], + "markers": "python_full_version >= '3.6.8'", + "version": "==3.0.9" + }, + "pypdf2": { + "hashes": [ + "sha256:3c7badd512c21711eb1789c2eadbf96279289c0f94452ee54a86473bfbefd732", + "sha256:7291a552ead2e7c2d556cce03bf71842fbbab478fcba13ae75ab1d59746b4dcb" + ], + "markers": "python_version >= '3.6'", + "version": "==2.11.1" + }, + "pyproj": { + "hashes": [ + "sha256:0fff9c3a991508f16027be27d153f6c5583d03799443639d13c681e60f49e2d7", + "sha256:12f62c20656ac9b6076ebb213e9a635d52f4f01fef95310121d337e62e910cb6", + "sha256:14ad113b5753c6057f9b2f3c85a6497cef7fa237c4328f2943c0223e98c1dde6", + "sha256:1f9c100fd0fd80edbc7e4daa303600a8cbef6f0de43d005617acb38276b88dc0", + "sha256:221d8939685e0c43ee594c9f04b6a73a10e8e1cc0e85f28be0b4eb2f1bc8777d", + "sha256:25a36e297f3e0524694d40259e3e895edc1a47492a0e30608268ffc1328e3f5d", + "sha256:2cb8592259ea54e7557523b079d3f2304081680bdb48bfbf0fd879ee6156129c", + "sha256:3b85acf09e5a9e35cd9ee72989793adb7089b4e611be02a43d3d0bda50ad116b", + "sha256:45554f47d1a12a84b0620e4abc08a2a1b5d9f273a4759eaef75e74788ec7162a", + "sha256:4688b4cd62cbd86b5e855f9e27d90fbb53f2b4c2ea1cd394a46919e1a4151b89", + "sha256:47ad53452ae1dc8b0bf1df920a210bb5616989085aa646592f8681f1d741a754", + "sha256:48787962232109bad8b72e27949037a9b03591228a6955f25dbe451233e8648a", + "sha256:4a23d84c5ffc383c7d9f0bde3a06fc1f6697b1b96725597f8f01e7b4bef0a2b5", + "sha256:4e161114bc92701647a83c4bbce79489984f12d980cabb365516e953d1450885", + "sha256:4fd425ee8b6781c249c7adb7daa2e6c41ce573afabe4f380f5eecd913b56a3be", + "sha256:52e54796e2d9554a5eb8f11df4748af1fbbc47f76aa234d6faf09216a84554c5", + "sha256:5816807ca0bdc7256558770c6206a6783a3f02bcf844f94ee245f197bb5f7285", + "sha256:65a0bcdbad95b3c00b419e5d75b1f7e450ec17349b5ea16bf7438ac1d50a12a2", + "sha256:77d5f519f3cdb94b026ecca626f78db4f041afe201cf082079c8c0092a30b087", + "sha256:82200b4569d68b421c079d2973475b58d5959306fe758b43366e79fe96facfe5", + "sha256:954b068136518b3174d0a99448056e97af62b63392a95c420894f7de2229dae6", + "sha256:9a496d9057b2128db9d733e66b206f2d5954bbae6b800d412f562d780561478c", + "sha256:a454a7c4423faa2a14e939d08ef293ee347fa529c9df79022b0585a6e1d8310c", + "sha256:a708445927ace9857f52c3ba67d2915da7b41a8fdcd9b8f99a4c9ed60a75eb33", + "sha256:aa5171f700f174777a9e9ed8f4655583243967c0f9cf2c90e3f54e54ff740134", + "sha256:ccb4b70ad25218027f77e0c8934d10f9b7cdf91d5e64080147743d58fddbc3c0", + "sha256:d94afed99f31673d3d19fe750283621e193e2a53ca9e0443bf9d092c3905833b", + "sha256:e7e609903572a56cca758bbaee5c1663c3e829ddce5eec4f368e68277e37022b", + "sha256:f343725566267a296b09ee7e591894f1fdc90f84f8ad5ec476aeb53bd4479c07", + "sha256:f80adda8c54b84271a93829477a01aa57bc178c834362e9f74e1de1b5033c74c" + ], + "markers": "python_version >= '3.8'", + "version": "==3.4.0" + }, + "pyrsistent": { + "hashes": [ + "sha256:0e3e1fcc45199df76053026a51cc59ab2ea3fc7c094c6627e93b7b44cdae2c8c", + "sha256:1b34eedd6812bf4d33814fca1b66005805d3640ce53140ab8bbb1e2651b0d9bc", + "sha256:4ed6784ceac462a7d6fcb7e9b663e93b9a6fb373b7f43594f9ff68875788e01e", + "sha256:5d45866ececf4a5fff8742c25722da6d4c9e180daa7b405dc0a2a2790d668c26", + "sha256:636ce2dc235046ccd3d8c56a7ad54e99d5c1cd0ef07d9ae847306c91d11b5fec", + "sha256:6455fc599df93d1f60e1c5c4fe471499f08d190d57eca040c0ea182301321286", + "sha256:6bc66318fb7ee012071b2792024564973ecc80e9522842eb4e17743604b5e045", + "sha256:7bfe2388663fd18bd8ce7db2c91c7400bf3e1a9e8bd7d63bf7e77d39051b85ec", + "sha256:7ec335fc998faa4febe75cc5268a9eac0478b3f681602c1f27befaf2a1abe1d8", + "sha256:914474c9f1d93080338ace89cb2acee74f4f666fb0424896fcfb8d86058bf17c", + "sha256:b568f35ad53a7b07ed9b1b2bae09eb15cdd671a5ba5d2c66caee40dbf91c68ca", + "sha256:cdfd2c361b8a8e5d9499b9082b501c452ade8bbf42aef97ea04854f4a3f43b22", + "sha256:d1b96547410f76078eaf66d282ddca2e4baae8964364abb4f4dcdde855cd123a", + "sha256:d4d61f8b993a7255ba714df3aca52700f8125289f84f704cf80916517c46eb96", + "sha256:d7a096646eab884bf8bed965bad63ea327e0d0c38989fc83c5ea7b8a87037bfc", + "sha256:df46c854f490f81210870e509818b729db4488e1f30f2a1ce1698b2295a878d1", + "sha256:e24a828f57e0c337c8d8bb9f6b12f09dfdf0273da25fda9e314f0b684b415a07", + "sha256:e4f3149fd5eb9b285d6bfb54d2e5173f6a116fe19172686797c056672689daf6", + "sha256:e92a52c166426efbe0d1ec1332ee9119b6d32fc1f0bbfd55d5c1088070e7fc1b", + "sha256:f87cc2863ef33c709e237d4b5f4502a62a00fab450c9e020892e8e2ede5847f5", + "sha256:fd8da6d0124efa2f67d86fa70c851022f87c98e205f0594e1fae044e7119a5a6" + ], + "markers": "python_version >= '3.7'", + "version": "==0.18.1" + }, + "python-dateutil": { + "hashes": [ + "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", + "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.8.2" + }, + "python-dotenv": { + "hashes": [ + "sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f", + "sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938" + ], + "index": "pypi", + "version": "==0.20.0" + }, + "python-json-logger": { + "hashes": [ + "sha256:3b03487b14eb9e4f77e4fc2a023358b5394b82fd89cecf5586259baed57d8c6f", + "sha256:764d762175f99fcc4630bd4853b09632acb60a6224acb27ce08cd70f0b1b81bd" + ], + "markers": "python_version >= '3.5'", + "version": "==2.0.4" + }, + "pytz": { + "hashes": [ + "sha256:335ab46900b1465e714b4fda4963d87363264eb662aab5e65da039c25f1f5b22", + "sha256:c4d88f472f54d615e9cd582a5004d1e5f624854a6a27a6211591c251f22a6914" + ], + "version": "==2022.5" + }, + "pyyaml": { + "hashes": [ + "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf", + "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696", + "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393", + "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77", + "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922", + "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5", + "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8", + "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10", + "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc", + "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018", + "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e", + "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253", + "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347", + "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183", + "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541", + "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb", + "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185", + "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc", + "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db", + "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa", + "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46", + "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122", + "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b", + "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63", + "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df", + "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc", + "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247", + "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6", + "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==5.4.1" + }, + "redis": { + "hashes": [ + "sha256:a52d5694c9eb4292770084fa8c863f79367ca19884b329ab574d5cb2036b3e54", + "sha256:ddf27071df4adf3821c4f2ca59d67525c3a82e5f268bed97b813cb4fabf87880" + ], + "version": "==4.3.4" + }, + "requests": { + "hashes": [ + "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", + "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" + ], + "markers": "python_version >= '3.7' and python_full_version < '4.0.0'", + "version": "==2.28.1" + }, + "rfc3339-validator": { + "hashes": [ + "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", + "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa" + ], + "version": "==0.1.4" + }, + "rfc3987": { + "hashes": [ + "sha256:10702b1e51e5658843460b189b185c0366d2cf4cff716f13111b0ea9fd2dce53", + "sha256:d3c4d257a560d544e9826b38bc81db676890c79ab9d7ac92b39c7a253d5ca733" + ], + "version": "==1.3.8" + }, + "rsa": { + "hashes": [ + "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2", + "sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9" + ], + "markers": "python_version >= '3.5' and python_full_version < '4.0.0'", + "version": "==4.7.2" + }, + "s3transfer": { + "hashes": [ + "sha256:7a6f4c4d1fdb9a2b640244008e142cbc2cd3ae34b386584ef044dd0f27101971", + "sha256:95c58c194ce657a5f4fb0b9e60a84968c808888aed628cd98ab8771fe1db98ed" + ], + "markers": "python_version >= '3.6'", + "version": "==0.5.2" + }, + "setuptools": { + "hashes": [ + "sha256:512e5536220e38146176efb833d4a62aa726b7bbff82cfbc8ba9eaa3996e0b17", + "sha256:f62ea9da9ed6289bfe868cd6845968a2c854d1427f8548d52cae02a42b4f0356" + ], + "markers": "python_version >= '3.7'", + "version": "==65.5.0" + }, + "shapely": { + "hashes": [ + "sha256:02dd5d7dc6e46515d88874134dc8fcdc65826bca93c3eecee59d1910c42c1b17", + "sha256:0b4ee3132ee90f07d63db3aea316c4c065ed7a26231458dda0874414a09d6ba3", + "sha256:0d885cb0cf670c1c834df3f371de8726efdf711f18e2a75da5cfa82843a7ab65", + "sha256:147066da0be41b147a61f8eb805dea3b13709dbc873a431ccd7306e24d712bc0", + "sha256:21776184516a16bf82a0c3d6d6a312b3cd15a4cabafc61ee01cf2714a82e8396", + "sha256:2e0a8c2e55f1be1312b51c92b06462ea89e6bb703fab4b114e7a846d941cfc40", + "sha256:2fd15397638df291c427a53d641d3e6fd60458128029c8c4f487190473a69a91", + "sha256:3480657460e939f45a7d359ef0e172a081f249312557fe9aa78c4fd3a362d993", + "sha256:370b574c78dc5af3a198a6da5d9b3d7c04654bd2ef7e80e80a3a0992dfb2d9cd", + "sha256:38f0fbbcb8ca20c16451c966c1f527cc43968e121c8a048af19ed3e339a921cd", + "sha256:4728666fff8cccc65a07448cae72c75a8773fea061c3f4f139c44adc429b18c3", + "sha256:48dcfffb9e225c0481120f4bdf622131c8c95f342b00b158cdbe220edbbe20b6", + "sha256:532a55ee2a6c52d23d6f7d1567c8f0473635f3b270262c44e1b0c88096827e22", + "sha256:5d7f85c2d35d39ff53c9216bc76b7641c52326f7e09aaad1789a3611a0f812f2", + "sha256:65b21243d8f6bcd421210daf1fabb9de84de2c04353c5b026173b88d17c1a581", + "sha256:66bdac74fbd1d3458fa787191a90fa0ae610f09e2a5ec398c36f968cc0ed743f", + "sha256:6d388c0c1bd878ed1af4583695690aa52234b02ed35f93a1c8486ff52a555838", + "sha256:6fe855e7d45685926b6ba00aaeb5eba5862611f7465775dacd527e081a8ced6d", + "sha256:753ed0e21ab108bd4282405b9b659f2e985e8502b1a72b978eaa51d3496dee19", + "sha256:783bad5f48e2708a0e2f695a34ed382e4162c795cb2f0368b39528ac1d6db7ed", + "sha256:78fb9d929b8ee15cfd424b6c10879ce1907f24e05fb83310fc47d2cd27088e40", + "sha256:84010db15eb364a52b74ea8804ef92a6a930dfc1981d17a369444b6ddec66efd", + "sha256:8d086591f744be483b34628b391d741e46f2645fe37594319e0a673cc2c26bcf", + "sha256:8e59817b0fe63d34baedaabba8c393c0090f061917d18fc0bcc2f621937a8f73", + "sha256:99a2f0da0109e81e0c101a2b4cd8412f73f5f299e7b5b2deaf64cd2a100ac118", + "sha256:99ab0ddc05e44acabdbe657c599fdb9b2d82e86c5493bdae216c0c4018a82dee", + "sha256:a23ef3882d6aa203dd3623a3d55d698f59bfbd9f8a3bfed52c2da05a7f0f8640", + "sha256:a354199219c8d836f280b88f2c5102c81bb044ccea45bd361dc38a79f3873714", + "sha256:a74631e511153366c6dbe3229fa93f877e3c87ea8369cd00f1d38c76b0ed9ace", + "sha256:ab38f7b5196ace05725e407cb8cab9ff66edb8e6f7bb36a398e8f73f52a7aaa2", + "sha256:adcf8a11b98af9375e32bff91de184f33a68dc48b9cb9becad4f132fa25cfa3c", + "sha256:b65f5d530ba91e49ffc7c589255e878d2506a8b96ffce69d3b7c4500a9a9eaf8", + "sha256:be9423d5a3577ac2e92c7e758bd8a2b205f5e51a012177a590bc46fc51eb4834", + "sha256:c2822111ddc5bcfb116e6c663e403579d0fe3f147d2a97426011a191c43a7458", + "sha256:c6a9a4a31cd6e86d0fbe8473ceed83d4fe760b19d949fb557ef668defafea0f6", + "sha256:d048f93e42ba578b82758c15d8ae037d08e69d91d9872bca5a1895b118f4e2b0", + "sha256:e9c30b311de2513555ab02464ebb76115d242842b29c412f5a9aa0cac57be9f6", + "sha256:ec14ceca36f67cb48b34d02d7f65a9acae15cd72b48e303531893ba4a960f3ea", + "sha256:ef3be705c3eac282a28058e6c6e5503419b250f482320df2172abcbea642c831" + ], + "markers": "python_version >= '3.6'", + "version": "==1.8.5.post1" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "smartypants": { + "hashes": [ + "sha256:8db97f7cbdf08d15b158a86037cd9e116b4cf37703d24e0419a0d64ca5808f0d" + ], + "version": "==2.0.1" + }, + "soupsieve": { + "hashes": [ + "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759", + "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d" + ], + "markers": "python_version >= '3.6'", + "version": "==2.3.2.post1" + }, + "sqlalchemy": { + "hashes": [ + "sha256:00dd998b43b282c71de46b061627b5edb9332510eb1edfc5017b9e4356ed44ea", + "sha256:08b47c971327e733ffd6bae2d4f50a7b761793efe69d41067fcba86282819eea", + "sha256:0992f3cc640ec0f88f721e426da884c34ff0a60eb73d3d64172e23dfadfc8a0b", + "sha256:0c956a5d1adb49a35d78ef0fae26717afc48a36262359bb5b0cbd7a3a247c26f", + "sha256:1ab08141d93de83559f6a7d9a962830f918623a885b3759ec2b9d1a531ff28fe", + "sha256:1cf03d37819dc17a388d313919daf32058d19ba1e592efdf14ce8cbd997e6023", + "sha256:2026632051a93997cf8f6fda14360f99230be1725b7ab2ef15be205a4b8a5430", + "sha256:23b693876ac7963b6bc7b1a5f3a2642f38d2624af834faad5933913928089d1b", + "sha256:26ee4dbac5dd7abf18bf3cd8f04e51f72c339caf702f68172d308888cd26c6c9", + "sha256:28b1791a30d62fc104070965f1a2866699c45bbf5adc0be0cf5f22935edcac58", + "sha256:2b64955850a14b9d481c17becf0d3f62fb1bb31ac2c45c2caf5ad06d9e811187", + "sha256:2cf50611ef4221ad587fb7a1708e61ff72966f84330c6317642e08d6db4138fd", + "sha256:44a660506080cc975e1dfa5776fe5f6315ddc626a77b50bf0eee18b0389ea265", + "sha256:4ec440990ab00650d0c7ea2c75bc225087afdd7ddcb248e3d934def4dff62762", + "sha256:63ad778f4e80913fb171247e4fa82123d0068615ae1d51a9791fc4284cb81748", + "sha256:69deec3a94de10062080d91e1ba69595efeafeafe68b996426dec9720031fb25", + "sha256:6b70d02bbe1adbbf715d2249cacf9ac17c6f8d22dfcb3f1a4fbc5bf64364da8a", + "sha256:885e11638946472b4a0a7db8e6df604b2cf64d23dc40eedc3806d869fcb18fae", + "sha256:959bf4390766a8696aa01285016c766b4eb676f712878aac5fce956dd49695d9", + "sha256:9ced2450c9fd016f9232d976661623e54c450679eeefc7aa48a3d29924a63189", + "sha256:a0b9e3d81f86ba04007f0349e373a5b8c81ec2047aadb8d669caf8c54a092461", + "sha256:a62c0ecbb9976550f26f7bf75569f425e661e7249349487f1483115e5fc893a6", + "sha256:b07fc38e6392a65935dc8b486229679142b2ea33c94059366b4d8b56f1e35a97", + "sha256:b41b87b929118838bafc4bb18cf3c5cd1b3be4b61cd9042e75174df79e8ac7a2", + "sha256:b7ccdca6cd167611f4a62a8c2c0c4285c2535640d77108f782ce3f3cccb70f3a", + "sha256:b7ff0a8bf0aec1908b92b8dfa1246128bf4f94adbdd3da6730e9c542e112542d", + "sha256:bb342c0e25cc8f78a0e7c692da3b984f072666b316fbbec2a0e371cb4dfef5f0", + "sha256:bf073c619b5a7f7cd731507d0fdc7329bee14b247a63b0419929e4acd24afea8", + "sha256:c8d974c991eef0cd29418a5957ae544559dc326685a6f26b3a914c87759bf2f4", + "sha256:c9d0f1a9538cc5e75f2ea0cb6c3d70155a1b7f18092c052e0d84105622a41b63", + "sha256:cdee4d475e35684d210dc6b430ff8ca2ed0636378ac19b457e2f6f350d1f5acc", + "sha256:cfa8ab4ba0c97ab6bcae1f0948497d14c11b6c6ecd1b32b8a79546a0823d8211", + "sha256:d259fa08e4b3ed952c01711268bcf6cd2442b0c54866d64aece122f83da77c6d", + "sha256:f2aa85aebc0ef6b342d5d3542f969caa8c6a63c8d36cf5098769158a9fa2123c", + "sha256:fa9e0d7832b7511b3b3fd0e67fac85ff11fd752834c143ca2364c9b778c0485a", + "sha256:fb4edb6c354eac0fcc07cb91797e142f702532dbb16c1d62839d6eec35f814cf" + ], + "index": "pypi", + "version": "==1.4.40" + }, + "statsd": { + "hashes": [ + "sha256:c610fb80347fca0ef62666d241bce64184bd7cc1efe582f9690e045c25535eaa", + "sha256:e3e6db4c246f7c59003e51c9720a51a7f39a396541cb9b147ff4b14d15b5dd1f" + ], + "version": "==3.3.0" + }, + "typing-extensions": { + "hashes": [ + "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa", + "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e" + ], + "markers": "python_version < '3.10'", + "version": "==4.4.0" + }, + "uri-template": { + "hashes": [ + "sha256:934e4d09d108b70eb8a24410af8615294d09d279ce0e7cbcdaef1bd21f932b06", + "sha256:f1699c77b73b925cf4937eae31ab282a86dc885c333f2e942513f08f691fc7db" + ], + "version": "==1.2.0" + }, + "urllib3": { + "hashes": [ + "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e", + "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_full_version < '4.0.0'", + "version": "==1.26.12" + }, + "vine": { + "hashes": [ + "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30", + "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e" + ], + "markers": "python_version >= '3.6'", + "version": "==5.0.0" + }, + "wcwidth": { + "hashes": [ + "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784", + "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83" + ], + "version": "==0.2.5" + }, + "webcolors": { + "hashes": [ + "sha256:16d043d3a08fd6a1b1b7e3e9e62640d09790dce80d2bdd4792a175b35fe794a9", + "sha256:d98743d81d498a2d3eaf165196e65481f0d2ea85281463d856b1e51b09f62dce" + ], + "version": "==1.12" + }, + "webencodings": { + "hashes": [ + "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", + "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" + ], + "version": "==0.5.1" + }, + "werkzeug": { + "hashes": [ + "sha256:1ce08e8093ed67d638d63879fd1ba3735817f7a80de3674d293f5984f25fb6e6", + "sha256:72a4b735692dd3135217911cbeaa1be5fa3f62bffb8745c5215420a03dc55255" + ], + "index": "pypi", + "version": "==2.1.2" + }, + "wrapt": { + "hashes": [ + "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3", + "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b", + "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4", + "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2", + "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656", + "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3", + "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff", + "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310", + "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a", + "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57", + "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069", + "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383", + "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe", + "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87", + "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d", + "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b", + "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907", + "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f", + "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0", + "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28", + "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1", + "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853", + "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc", + "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3", + "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3", + "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164", + "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1", + "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c", + "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1", + "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7", + "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1", + "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320", + "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed", + "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1", + "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248", + "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c", + "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456", + "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77", + "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef", + "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1", + "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7", + "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86", + "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4", + "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d", + "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d", + "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8", + "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5", + "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471", + "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00", + "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68", + "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3", + "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d", + "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735", + "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d", + "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569", + "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7", + "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59", + "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5", + "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb", + "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b", + "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f", + "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462", + "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015", + "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.14.1" + }, + "zipp": { + "hashes": [ + "sha256:3a7af91c3db40ec72dd9d154ae18e008c69efe8ca88dde4f9a731bb82fe2f9eb", + "sha256:972cfa31bc2fedd3fa838a51e9bc7e64b7fb725a8c00e7431554311f180e9980" + ], + "markers": "python_version >= '3.7'", + "version": "==3.9.0" + } + }, + "develop": { + "attrs": { + "hashes": [ + "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", + "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" + ], + "index": "pypi", + "version": "==21.4.0" + }, + "boto3": { + "hashes": [ + "sha256:15733c2bbedce7a36fcf1749560c72c3ee90785aa6302a98658c7bffdcbe1f2a", + "sha256:ea8ebcea4ccb70d1cf57526d9eec6012c76796f28ada3e9cc1d89178683d8107" + ], + "index": "pypi", + "version": "==1.23.8" + }, + "botocore": { + "hashes": [ + "sha256:620851daf1245af5bc28137aa821375bac964aa0eddc482437c783fe01e298fc", + "sha256:e786722cb14de7319331cc55e9092174de66a768559700ef656d05ff41b3e24f" + ], + "index": "pypi", + "version": "==1.26.8" + }, + "certifi": { + "hashes": [ + "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7", + "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a" + ], + "index": "pypi", + "version": "==2022.5.18.1" + }, + "cffi": { + "hashes": [ + "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3", + "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2", + "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636", + "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20", + "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728", + "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27", + "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66", + "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443", + "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0", + "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7", + "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39", + "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605", + "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a", + "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37", + "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029", + "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139", + "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc", + "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df", + "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14", + "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880", + "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2", + "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a", + "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e", + "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474", + "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024", + "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8", + "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0", + "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e", + "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a", + "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e", + "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032", + "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6", + "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e", + "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b", + "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e", + "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954", + "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962", + "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c", + "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4", + "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55", + "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962", + "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023", + "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c", + "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6", + "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8", + "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382", + "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7", + "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc", + "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997", + "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796" + ], + "index": "pypi", + "version": "==1.15.0" + }, + "charset-normalizer": { + "hashes": [ + "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", + "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df" + ], + "index": "pypi", + "version": "==2.0.12" + }, + "coverage": { + "extras": [ + "toml" + ], + "hashes": [ + "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79", + "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a", + "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f", + "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a", + "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa", + "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398", + "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba", + "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d", + "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf", + "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b", + "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518", + "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d", + "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795", + "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2", + "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e", + "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32", + "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745", + "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b", + "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e", + "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d", + "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f", + "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660", + "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62", + "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6", + "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04", + "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c", + "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5", + "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef", + "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc", + "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae", + "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578", + "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466", + "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4", + "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91", + "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0", + "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4", + "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b", + "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe", + "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b", + "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75", + "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b", + "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c", + "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72", + "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b", + "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f", + "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e", + "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53", + "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3", + "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84", + "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987" + ], + "markers": "python_version >= '3.7'", + "version": "==6.5.0" + }, + "cryptography": { + "hashes": [ + "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a", + "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f", + "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0", + "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407", + "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7", + "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6", + "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153", + "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750", + "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad", + "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6", + "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b", + "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5", + "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a", + "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d", + "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d", + "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294", + "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0", + "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a", + "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac", + "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61", + "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013", + "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e", + "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb", + "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9", + "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd", + "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818" + ], + "markers": "python_version >= '3.6'", + "version": "==38.0.1" + }, + "execnet": { + "hashes": [ + "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5", + "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.9.0" + }, + "flake8": { + "hashes": [ + "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d", + "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d" + ], + "index": "pypi", + "version": "==4.0.1" + }, + "flake8-bugbear": { + "hashes": [ + "sha256:ec374101cddf65bd7a96d393847d74e58d3b98669dbf9768344c39b6290e8bd6", + "sha256:f7c080563fca75ee6b205d06b181ecba22b802babb96b0b084cc7743d6908a55" + ], + "index": "pypi", + "version": "==22.4.25" + }, + "freezegun": { + "hashes": [ + "sha256:15103a67dfa868ad809a8f508146e396be2995172d25f927e48ce51c0bf5cb09", + "sha256:b4c64efb275e6bc68dc6e771b17ffe0ff0f90b81a2a5189043550b6519926ba4" + ], + "index": "pypi", + "version": "==1.2.1" + }, + "idna": { + "hashes": [ + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + ], + "version": "==3.4" + }, + "iniconfig": { + "hashes": [ + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + ], + "version": "==1.1.1" + }, + "isort": { + "hashes": [ + "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7", + "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951" + ], + "index": "pypi", + "version": "==5.10.1" + }, + "jinja2": { + "hashes": [ + "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", + "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" + ], + "markers": "python_version >= '3.7'", + "version": "==3.1.2" + }, + "jinja2-cli": { + "extras": [ + "yaml" + ], + "hashes": [ + "sha256:a16bb1454111128e206f568c95938cdef5b5a139929378f72bb8cf6179e18e50", + "sha256:b91715c79496beaddad790171e7258a87db21c1a0b6d2b15bca3ba44b74aac5d" + ], + "index": "pypi", + "version": "==0.8.2" + }, + "jmespath": { + "hashes": [ + "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", + "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe" + ], + "markers": "python_version >= '3.7'", + "version": "==1.0.1" + }, + "markupsafe": { + "hashes": [ + "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", + "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", + "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", + "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", + "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", + "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", + "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", + "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", + "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", + "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", + "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", + "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", + "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", + "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", + "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", + "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", + "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", + "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", + "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", + "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", + "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", + "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", + "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", + "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", + "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", + "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", + "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", + "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", + "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", + "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", + "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", + "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", + "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", + "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", + "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", + "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", + "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", + "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", + "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", + "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.1" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "moto": { + "hashes": [ + "sha256:8928ec168e5fd88b1127413b2fa570a80d45f25182cdad793edd208d07825269", + "sha256:ba683e70950b6579189bc12d74c1477aa036c090c6ad8b151a22f5896c005113" + ], + "index": "pypi", + "version": "==3.1.9" + }, + "packaging": { + "hashes": [ + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + ], + "markers": "python_version >= '3.6'", + "version": "==21.3" + }, + "pluggy": { + "hashes": [ + "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", + "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" + ], + "markers": "python_version >= '3.6'", + "version": "==1.0.0" + }, + "py": { + "hashes": [ + "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", + "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.11.0" + }, + "pycodestyle": { + "hashes": [ + "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20", + "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==2.8.0" + }, + "pycparser": { + "hashes": [ + "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", + "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" + ], + "version": "==2.21" + }, + "pyflakes": { + "hashes": [ + "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c", + "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.4.0" + }, + "pyparsing": { + "hashes": [ + "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", + "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" + ], + "markers": "python_full_version >= '3.6.8'", + "version": "==3.0.9" + }, + "pytest": { + "hashes": [ + "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c", + "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45" + ], + "index": "pypi", + "version": "==7.1.2" + }, + "pytest-cov": { + "hashes": [ + "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6", + "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470" + ], + "index": "pypi", + "version": "==3.0.0" + }, + "pytest-env": { + "hashes": [ + "sha256:7e94956aef7f2764f3c147d216ce066bf6c42948bb9e293169b1b1c880a580c2" + ], + "index": "pypi", + "version": "==0.6.2" + }, + "pytest-forked": { + "hashes": [ + "sha256:8b67587c8f98cbbadfdd804539ed5455b6ed03802203485dd2f53c1422d7440e", + "sha256:bbbb6717efc886b9d64537b41fb1497cfaf3c9601276be8da2cccfea5a3c8ad8" + ], + "markers": "python_version >= '3.6'", + "version": "==1.4.0" + }, + "pytest-mock": { + "hashes": [ + "sha256:5112bd92cc9f186ee96e1a92efc84969ea494939c3aead39c50f421c4cc69534", + "sha256:6cff27cec936bf81dc5ee87f07132b807bcda51106b5ec4b90a04331cba76231" + ], + "index": "pypi", + "version": "==3.7.0" + }, + "pytest-xdist": { + "hashes": [ + "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf", + "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65" + ], + "index": "pypi", + "version": "==2.5.0" + }, + "python-dateutil": { + "hashes": [ + "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", + "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.8.2" + }, + "pytz": { + "hashes": [ + "sha256:335ab46900b1465e714b4fda4963d87363264eb662aab5e65da039c25f1f5b22", + "sha256:c4d88f472f54d615e9cd582a5004d1e5f624854a6a27a6211591c251f22a6914" + ], + "version": "==2022.5" + }, + "pyyaml": { + "hashes": [ + "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf", + "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696", + "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393", + "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77", + "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922", + "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5", + "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8", + "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10", + "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc", + "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018", + "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e", + "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253", + "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347", + "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183", + "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541", + "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb", + "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185", + "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc", + "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db", + "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa", + "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46", + "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122", + "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b", + "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63", + "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df", + "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc", + "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247", + "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6", + "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==5.4.1" + }, + "requests": { + "hashes": [ + "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", + "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" + ], + "markers": "python_version >= '3.7' and python_full_version < '4.0.0'", + "version": "==2.28.1" + }, + "requests-mock": { + "hashes": [ + "sha256:0a2d38a117c08bb78939ec163522976ad59a6b7fdd82b709e23bb98004a44970", + "sha256:8d72abe54546c1fc9696fa1516672f1031d72a55a1d66c85184f972a24ba0eba" + ], + "index": "pypi", + "version": "==1.9.3" + }, + "responses": { + "hashes": [ + "sha256:396acb2a13d25297789a5866b4881cf4e46ffd49cc26c43ab1117f40b973102e", + "sha256:dcf294d204d14c436fddcc74caefdbc5764795a40ff4e6a7740ed8ddbf3294be" + ], + "markers": "python_version >= '3.7'", + "version": "==0.22.0" + }, + "s3transfer": { + "hashes": [ + "sha256:7a6f4c4d1fdb9a2b640244008e142cbc2cd3ae34b386584ef044dd0f27101971", + "sha256:95c58c194ce657a5f4fb0b9e60a84968c808888aed628cd98ab8771fe1db98ed" + ], + "markers": "python_version >= '3.6'", + "version": "==0.5.2" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "toml": { + "hashes": [ + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.10.2" + }, + "tomli": { + "hashes": [ + "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.1" + }, + "types-toml": { + "hashes": [ + "sha256:8300fd093e5829eb9c1fba69cee38130347d4b74ddf32d0a7df650ae55c2b599", + "sha256:b7e7ea572308b1030dc86c3ba825c5210814c2825612ec679eb7814f8dd9295a" + ], + "version": "==0.10.8" + }, + "urllib3": { + "hashes": [ + "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e", + "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_full_version < '4.0.0'", + "version": "==1.26.12" + }, + "werkzeug": { + "hashes": [ + "sha256:1ce08e8093ed67d638d63879fd1ba3735817f7a80de3674d293f5984f25fb6e6", + "sha256:72a4b735692dd3135217911cbeaa1be5fa3f62bffb8745c5215420a03dc55255" + ], + "index": "pypi", + "version": "==2.1.2" + }, + "xmltodict": { + "hashes": [ + "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56", + "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852" + ], + "markers": "python_version >= '3.4'", + "version": "==0.13.0" + } + } +} diff --git a/requirements.in b/requirements.in index fc1679f3f..dbcc0f8a6 100644 --- a/requirements.in +++ b/requirements.in @@ -32,8 +32,8 @@ notifications-python-client==6.3.0 # PaaS awscli-cwlogs==1.4.6 -notifications-utils @ git+https://github.com/GSA/notifications-utils.git +notifications-utils @ git+https://github.com/GSA/notifications-utils.git#egg=notifications-utils # gds-metrics requires prometheseus 0.2.0, override that requirement as 0.7.1 brings significant performance gains prometheus-client==0.14.1 -git+https://github.com/alphagov/gds_metrics_python.git@6f1840a57b6fb1ee40b7e84f2f18ec229de8aa72 +git+https://github.com/alphagov/gds_metrics_python.git@6f1840a57b6fb1ee40b7e84f2f18ec229de8aa72#egg=gds-metrics From 8ad130893d8c8916be7198a60abdca3208c8b0da Mon Sep 17 00:00:00 2001 From: stvnrlly Date: Tue, 25 Oct 2022 19:57:31 +0000 Subject: [PATCH 08/19] trim rollback from makefile --- Makefile | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Makefile b/Makefile index 1de8c9589..fcd1ce054 100644 --- a/Makefile +++ b/Makefile @@ -100,12 +100,6 @@ cf-login: ## Log in to Cloud Foundry cf-check-api-db-migration-task: ## Get the status for the last notifications-api task @cf curl /v3/apps/`cf app --guid notifications-api`/tasks?order_by=-created_at | jq -r ".resources[0].state" -# .PHONY: cf-rollback -# cf-rollback: ## Rollbacks the app to the previous release -# $(if ${CF_APP},,$(error Must specify CF_APP)) -# rm ${CF_MANIFEST_PATH} -# cf cancel-deployment ${CF_APP} - .PHONY: check-if-migrations-to-run check-if-migrations-to-run: @echo $(shell python3 scripts/check_if_new_migration.py) From d27401c7a084e35be474d831559cddc929e8d15d Mon Sep 17 00:00:00 2001 From: stvnrlly Date: Wed, 26 Oct 2022 14:05:37 +0000 Subject: [PATCH 09/19] more pipenv transition --- .github/workflows/checks.yml | 2 + .github/workflows/daily_checks.yml | 2 + Makefile | 50 ++--- Pipfile.lock | 55 ++++++ README.md | 13 +- requirements.in | 39 ---- requirements.txt | 297 ----------------------------- requirements_for_test.txt | 14 -- 8 files changed, 80 insertions(+), 392 deletions(-) delete mode 100644 requirements.in delete mode 100644 requirements.txt delete mode 100644 requirements_for_test.txt diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 57e11688e..6fcdf8958 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -71,6 +71,8 @@ jobs: steps: - uses: actions/checkout@v3 - uses: ./.github/actions/setup-project + - name: Create requirements.txt + run: pipenv requirements - uses: trailofbits/gh-action-pip-audit@v1.0.0 with: inputs: requirements.txt diff --git a/.github/workflows/daily_checks.yml b/.github/workflows/daily_checks.yml index 06dd0bc19..1449ef3d5 100644 --- a/.github/workflows/daily_checks.yml +++ b/.github/workflows/daily_checks.yml @@ -38,6 +38,8 @@ jobs: steps: - uses: actions/checkout@v3 - uses: ./.github/actions/setup-project + - name: Create requirements.txt + run: pipenv requirements - uses: trailofbits/gh-action-pip-audit@v1.0.0 with: inputs: requirements.txt diff --git a/Makefile b/Makefile index fcd1ce054..7bc3da75c 100644 --- a/Makefile +++ b/Makefile @@ -7,18 +7,14 @@ 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) -CF_SPACE ?= ${DEPLOY_ENV} -CF_HOME ?= ${HOME} -$(eval export CF_HOME) - - ## DEVELOPMENT .PHONY: bootstrap -bootstrap: generate-version-file ## Set up everything to run the app - pip3 install -r requirements_for_test.txt +bootstrap: ## Set up everything to run the app + generate-version-file + pipenv install ---dev createdb notification_api || true - (flask db upgrade) || true + (pipenv run flask db upgrade) || true .PHONY: bootstrap-with-docker bootstrap-with-docker: ## Build the image to run the app in Docker @@ -26,31 +22,23 @@ bootstrap-with-docker: ## Build the image to run the app in Docker .PHONY: run-flask run-flask: ## Run flask - flask run -p 6011 --host=0.0.0.0 + pipenv run flask run -p 6011 --host=0.0.0.0 .PHONY: run-celery run-celery: ## Run celery, TODO remove purge for staging/prod - celery -A run_celery.notify_celery purge -f - celery \ + pipenv run celery -A run_celery.notify_celery purge -f + pipenv run celery \ -A run_celery.notify_celery worker \ --pidfile="/tmp/celery.pid" \ --loglevel=INFO \ --concurrency=4 -.PHONY: run-celery-with-docker -run-celery-with-docker: ## Run celery in Docker container (useful if you can't install pycurl locally) - ./scripts/run_with_docker.sh make run-celery - .PHONY: run-celery-beat run-celery-beat: ## Run celery beat - celery \ + pipenv run celery \ -A run_celery.notify_celery beat \ --loglevel=INFO -.PHONY: run-celery-beat-with-docker -run-celery-beat-with-docker: ## Run celery beat in Docker container (useful if you can't install pycurl locally) - ./scripts/run_with_docker.sh make run-celery-beat - .PHONY: help help: @cat $(MAKEFILE_LIST) | grep -E '^[a-zA-Z_-]+:.*?## .*$$' | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' @@ -67,12 +55,14 @@ test: ## Run tests .PHONY: freeze-requirements freeze-requirements: ## Pin all requirements including sub dependencies into requirements.txt - pip install --upgrade pip-tools - pip-compile requirements.in + pipenv lock + pipenv requirements .PHONY: audit audit: pip install --upgrade pip-audit + pipenv requirements > requirements.txt + pipenv requirements --dev > requirements_for_test.txt pip-audit -r requirements.txt -l --ignore-vuln PYSEC-2022-237 -pip-audit -r requirements_for_test.txt -l @@ -88,22 +78,6 @@ clean: ## DEPLOYMENT -.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: cf-check-api-db-migration-task -cf-check-api-db-migration-task: ## Get the status for the last notifications-api task - @cf curl /v3/apps/`cf app --guid notifications-api`/tasks?order_by=-created_at | jq -r ".resources[0].state" - -.PHONY: check-if-migrations-to-run -check-if-migrations-to-run: - @echo $(shell python3 scripts/check_if_new_migration.py) - # .PHONY: cf-deploy-failwhale # cf-deploy-failwhale: # $(if ${CF_SPACE},,$(error Must target space, eg `make preview cf-deploy-failwhale`)) diff --git a/Pipfile.lock b/Pipfile.lock index 8cd0bf67e..565227cdb 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,11 @@ { "_meta": { "hash": { +<<<<<<< Updated upstream "sha256": "eca9a39871c8db3e82fb384c39afc89fdc3c145c87653f5090927e578618ce11" +======= + "sha256": "ce99b649bae4b10b084aacc936b72e01957eb9354cbc420d684590133c2da004" +>>>>>>> Stashed changes }, "pipfile-spec": 6, "requires": { @@ -370,11 +374,19 @@ "version": "==0.4.0" }, "flask-sqlalchemy": { +<<<<<<< Updated upstream +======= + "git": "https://github.com/pallets-eco/flask-sqlalchemy.git", +>>>>>>> Stashed changes "hashes": [ "sha256:2bda44b43e7cacb15d4e05ff3cc1f8bc97936cc464623424102bfc2c35e95912", "sha256:f12c3d4cc5cc7fdcc148b9527ea05671718c3ea45d50c7e732cceb33f574b390" ], +<<<<<<< Updated upstream "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", +======= + "ref": "aa7a61a5357cf6f5dcc135d98c781192457aa6fa", +>>>>>>> Stashed changes "version": "==2.5.1" }, "flask-sqlalchemy==2-5-1": { @@ -482,6 +494,17 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==1.1.3.post0" }, +<<<<<<< Updated upstream +======= + "gunicorn": { + "extras": [ + "eventlet" + ], + "git": "https://github.com/benoitc/gunicorn.git", + "ref": "1299ea9e967a61ae2edebe191082fd169b864c64", + "version": "==20.1.0" + }, +>>>>>>> Stashed changes "gunicorn[eventlet]==20-1-0": { "git": "https://github.com/benoitc/gunicorn.git", "ref": "1299ea9e967a61ae2edebe191082fd169b864c64" @@ -732,7 +755,11 @@ }, "notifications-utils": { "git": "https://github.com/GSA/notifications-utils.git", +<<<<<<< Updated upstream "ref": "90c12da575f4e481452d4fcd2a594204b0c28249" +======= + "ref": "2cdffe3fa2417b61ce3d714dc5a2d67de6632bdd" +>>>>>>> Stashed changes }, "orderedset": { "hashes": [ @@ -1034,7 +1061,11 @@ "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" ], +<<<<<<< Updated upstream "markers": "python_version >= '3.7' and python_full_version < '4.0.0'", +======= + "markers": "python_version >= '3.7' and python_version < '4'", +>>>>>>> Stashed changes "version": "==2.28.1" }, "rfc3339-validator": { @@ -1056,7 +1087,11 @@ "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2", "sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9" ], +<<<<<<< Updated upstream "markers": "python_version >= '3.5' and python_full_version < '4.0.0'", +======= + "markers": "python_version >= '3.5' and python_version < '4'", +>>>>>>> Stashed changes "version": "==4.7.2" }, "s3transfer": { @@ -1211,7 +1246,11 @@ "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e", "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" ], +<<<<<<< Updated upstream "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_full_version < '4.0.0'", +======= + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'", +>>>>>>> Stashed changes "version": "==1.26.12" }, "vine": { @@ -1323,11 +1362,19 @@ }, "zipp": { "hashes": [ +<<<<<<< Updated upstream "sha256:3a7af91c3db40ec72dd9d154ae18e008c69efe8ca88dde4f9a731bb82fe2f9eb", "sha256:972cfa31bc2fedd3fa838a51e9bc7e64b7fb725a8c00e7431554311f180e9980" ], "markers": "python_version >= '3.7'", "version": "==3.9.0" +======= + "sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1", + "sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8" + ], + "markers": "python_version >= '3.7'", + "version": "==3.10.0" +>>>>>>> Stashed changes } }, "develop": { @@ -1817,7 +1864,11 @@ "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" ], +<<<<<<< Updated upstream "markers": "python_version >= '3.7' and python_full_version < '4.0.0'", +======= + "markers": "python_version >= '3.7' and python_version < '4'", +>>>>>>> Stashed changes "version": "==2.28.1" }, "requests-mock": { @@ -1880,7 +1931,11 @@ "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e", "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" ], +<<<<<<< Updated upstream "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_full_version < '4.0.0'", +======= + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'", +>>>>>>> Stashed changes "version": "==1.26.12" }, "werkzeug": { diff --git a/README.md b/README.md index cb0a95a21..1bd8ed0c4 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This repo contains: - A public-facing REST API for Notify, which teams can integrate with using [API clients built by UK](https://www.notifications.service.gov.uk/documentation) - An internal-only REST API built using Flask to manage services, users, templates, etc., which the [admin UI](http://github.com/18F/notifications-admin) talks to) -- Asynchronous workers built using Celery to put things on queues and read them off to be processed, sent to providers, updated, etc +- Asynchronous workers built using Celery to put things on queues and read them off to be processed, sent to providers, updated, etc. ## Local setup @@ -17,7 +17,7 @@ This repo contains: 1. Install dependencies into a virtual environment ``` - pipenv install --with dev + pipenv install --dev createdb notification_api flask db upgrade ``` @@ -46,7 +46,12 @@ This repo contains: If you're working in VS Code, you can also leverage Docker for a containerized dev environment -1. Create .env file as described in the .env section below. +1. Create the .env file + + ``` + cp sample.env .env + # follow the instructions in .env + ``` 1. Install the Remote-Containers plug-in in VS Code @@ -56,7 +61,7 @@ If you're working in VS Code, you can also leverage Docker for a containerized d 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-api** folder (note: this is a *subfolder* of notification-api). This will startup the container in a new window, replacing the current one. -1. Wait a few minutes while things happen +1. Wait a few minutes while things happen 🍵 1. Open a VS Code terminal and run the Flask application: diff --git a/requirements.in b/requirements.in deleted file mode 100644 index dbcc0f8a6..000000000 --- a/requirements.in +++ /dev/null @@ -1,39 +0,0 @@ -# Run `make freeze-requirements` to update requirements.txt -# with package version changes made in requirements.in - -cffi==1.15.0 -celery[redis]==5.2.7 -Flask-Bcrypt==1.0.1 -flask-marshmallow==0.14.0 -Flask-Migrate==3.1.0 -git+https://github.com/pallets-eco/flask-sqlalchemy.git@aa7a61a5357cf6f5dcc135d98c781192457aa6fa#egg=Flask-SQLAlchemy==2.5.1 -Flask==2.1.2 -click-datetime==0.2 -# 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 -iso8601==1.0.2 -itsdangerous==2.1.2 -jsonschema[format]==4.5.1 -marshmallow-sqlalchemy==0.28.1 -marshmallow==3.15.0 -psycopg2-binary==2.9.3 -PyJWT==2.4.0 -SQLAlchemy==1.4.40 -cachetools==5.1.0 -beautifulsoup4==4.11.1 -lxml==4.9.1 -defusedxml==0.7.1 -Werkzeug==2.1.1 -python-dotenv==0.20.0 -oscrypto==1.3.0 - -notifications-python-client==6.3.0 - -# PaaS -awscli-cwlogs==1.4.6 - -notifications-utils @ git+https://github.com/GSA/notifications-utils.git#egg=notifications-utils - -# gds-metrics requires prometheseus 0.2.0, override that requirement as 0.7.1 brings significant performance gains -prometheus-client==0.14.1 -git+https://github.com/alphagov/gds_metrics_python.git@6f1840a57b6fb1ee40b7e84f2f18ec229de8aa72#egg=gds-metrics diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index c5e41644e..000000000 --- a/requirements.txt +++ /dev/null @@ -1,297 +0,0 @@ -# -# This file is autogenerated by pip-compile with python 3.9 -# To update, run: -# -# pip-compile requirements.in -# -alembic==1.7.7 - # via flask-migrate -amqp==5.1.1 - # via kombu -arrow==1.2.2 - # via isoduration -asn1crypto==1.5.1 - # via oscrypto -async-timeout==4.0.2 - # via redis -attrs==21.4.0 - # via jsonschema -awscli==1.24.8 - # via awscli-cwlogs -awscli-cwlogs==1.4.6 - # via -r requirements.in -bcrypt==3.2.2 - # via flask-bcrypt -beautifulsoup4==4.11.1 - # via -r requirements.in -billiard==3.6.4.0 - # via celery -bleach==4.1.0 - # via notifications-utils -blinker==1.4 - # via gds-metrics -boto3==1.23.8 - # via notifications-utils -botocore==1.26.8 - # via - # awscli - # boto3 - # s3transfer -cachetools==5.1.0 - # via - # -r requirements.in - # notifications-utils -celery[redis]==5.2.7 - # via -r requirements.in -certifi==2022.5.18.1 - # via - # pyproj - # requests -cffi==1.15.0 - # via - # -r requirements.in - # bcrypt -charset-normalizer==2.0.12 - # via requests -click==8.1.3 - # via - # celery - # click-datetime - # click-didyoumean - # click-plugins - # click-repl - # flask -click-datetime==0.2 - # via -r requirements.in -click-didyoumean==0.3.0 - # via celery -click-plugins==1.1.1 - # via celery -click-repl==0.2.0 - # via celery -colorama==0.4.4 - # via awscli -defusedxml==0.7.1 - # via -r requirements.in -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 -eventlet==0.33.1 - # via gunicorn -flask==2.1.2 - # via - # -r requirements.in - # flask-bcrypt - # flask-marshmallow - # flask-migrate - # flask-redis - # flask-sqlalchemy - # gds-metrics - # notifications-utils -flask-bcrypt==1.0.1 - # via -r requirements.in -flask-marshmallow==0.14.0 - # via -r requirements.in -flask-migrate==3.1.0 - # via -r requirements.in -flask-redis==0.4.0 - # via notifications-utils -flask-sqlalchemy @ git+https://github.com/pallets-eco/flask-sqlalchemy.git@aa7a61a5357cf6f5dcc135d98c781192457aa6fa - # via - # -r requirements.in - # flask-migrate -fqdn==1.5.1 - # via jsonschema -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 notifications-utils -greenlet==1.1.2 - # via - # eventlet - # sqlalchemy -gunicorn @ git+https://github.com/benoitc/gunicorn.git@1299ea9e967a61ae2edebe191082fd169b864c64 - # via -r requirements.in -idna==3.3 - # via - # jsonschema - # requests -importlib-metadata==4.12.0 - # via flask -iso8601==1.0.2 - # via -r requirements.in -isoduration==20.11.0 - # via jsonschema -itsdangerous==2.1.2 - # via - # -r requirements.in - # flask - # notifications-utils -jinja2==3.1.2 - # via - # flask - # notifications-utils -jmespath==1.0.0 - # via - # boto3 - # botocore -jsonpointer==2.3 - # via jsonschema -jsonschema[format]==4.5.1 - # via -r requirements.in -kombu==5.2.4 - # via celery -lxml==4.9.1 - # via -r requirements.in -mako==1.2.2 - # via alembic -markupsafe==2.1.1 - # via - # jinja2 - # mako -marshmallow==3.15.0 - # via - # -r requirements.in - # flask-marshmallow - # marshmallow-sqlalchemy -marshmallow-sqlalchemy==0.28.1 - # via -r requirements.in -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 - # via -r requirements.in -orderedset==2.0.3 - # via notifications-utils -oscrypto==1.3.0 - # via -r requirements.in -packaging==21.3 - # via - # bleach - # marshmallow - # marshmallow-sqlalchemy - # redis -phonenumbers==8.12.48 - # via notifications-utils -prometheus-client==0.14.1 - # via - # -r requirements.in - # gds-metrics -prompt-toolkit==3.0.29 - # via click-repl -psycopg2-binary==2.9.3 - # via -r requirements.in -pyasn1==0.4.8 - # via rsa -pycparser==2.21 - # via cffi -pyjwt==2.4.0 - # via - # -r requirements.in - # notifications-python-client -pyparsing==3.0.9 - # via packaging -pypdf2==2.0.0 - # via notifications-utils -pyproj==3.3.1 - # via notifications-utils -pyrsistent==0.18.1 - # via jsonschema -python-dateutil==2.8.2 - # via - # arrow - # 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 - # celery - # notifications-utils -pyyaml==5.4.1 - # via - # awscli - # notifications-utils -redis==4.3.1 - # via - # celery - # flask-redis -requests==2.27.1 - # via - # awscli-cwlogs - # govuk-bank-holidays - # notifications-python-client - # notifications-utils -rfc3339-validator==0.1.4 - # via jsonschema -rfc3987==1.3.8 - # via jsonschema -rsa==4.7.2 - # via awscli -s3transfer==0.5.2 - # via - # awscli - # boto3 -shapely==1.8.2 - # via notifications-utils -six==1.16.0 - # via - # awscli-cwlogs - # bleach - # click-repl - # eventlet - # flask-marshmallow - # python-dateutil - # rfc3339-validator -smartypants==2.0.1 - # via notifications-utils -soupsieve==2.3.2.post1 - # via beautifulsoup4 -sqlalchemy==1.4.40 - # via - # -r requirements.in - # alembic - # flask-sqlalchemy - # marshmallow-sqlalchemy -statsd==3.3.0 - # via notifications-utils -typing-extensions==4.3.0 - # via pypdf2 -uri-template==1.2.0 - # via jsonschema -urllib3==1.26.9 - # via - # botocore - # requests -vine==5.0.0 - # via - # amqp - # celery - # kombu -wcwidth==0.2.5 - # via prompt-toolkit -webcolors==1.12 - # via jsonschema -webencodings==0.5.1 - # via bleach -werkzeug==2.1.1 - # via - # -r requirements.in - # flask -wrapt==1.14.1 - # via deprecated -zipp==3.8.1 - # via importlib-metadata - -# The following packages are considered to be unsafe in a requirements file: -# setuptools diff --git a/requirements_for_test.txt b/requirements_for_test.txt deleted file mode 100644 index 3c6756bc1..000000000 --- a/requirements_for_test.txt +++ /dev/null @@ -1,14 +0,0 @@ ---requirement requirements.txt -flake8==4.0.1 -flake8-bugbear==22.4.25 -isort==5.10.1 -moto==3.1.9 -pytest==7.1.2 -pytest-env==0.6.2 -pytest-mock==3.7.0 -pytest-cov==3.0.0 -pytest-xdist==2.5.0 -freezegun==1.2.1 -requests-mock==1.9.3 -# used for creating manifest file locally -jinja2-cli[yaml]==0.8.2 From 38270c1c5c201a53ed46f77e9edd8a7efaf89b30 Mon Sep 17 00:00:00 2001 From: stvnrlly Date: Wed, 26 Oct 2022 14:09:21 +0000 Subject: [PATCH 10/19] expand .cfignore and remove deploy-exclude.lst --- .cfignore | 110 ++++++++++++++++++++++++++++++++++++++++++++- deploy-exclude.lst | 13 ------ 2 files changed, 109 insertions(+), 14 deletions(-) mode change 120000 => 100644 .cfignore delete mode 100644 deploy-exclude.lst diff --git a/.cfignore b/.cfignore deleted file mode 120000 index 3e4e48b0b..000000000 --- a/.cfignore +++ /dev/null @@ -1 +0,0 @@ -.gitignore \ No newline at end of file diff --git a/.cfignore b/.cfignore new file mode 100644 index 000000000..fcbe7a227 --- /dev/null +++ b/.cfignore @@ -0,0 +1,109 @@ +# from deploy-exclude.lst + +*__pycache__* +.git/* +app/assets/* +bower_components/* +cache/* +.cache/* +node_modules/* +target/* +venv/* +build/* +.envrc +tests/.cache/* +.cf/* + +# from .gitignore + +queues.csv + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +.venv/ +venv/ +venv-freeze/ + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg +/cache + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache +coverage.xml +test_results.xml +*,cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ +.idea/ +.vscode + +# Mac +*.DS_Store +environment.sh +.envrc +.env +.env* +varsfile + +celerybeat-schedule + +# CloudFoundry +.cf +varsfile* +.secret* + +/scripts/run_my_tests.sh + +# Terraform +.terraform.lock.hcl +**/.terraform/* +secrets.auto.tfvars +terraform.tfstate +terraform.tfstate.backup diff --git a/deploy-exclude.lst b/deploy-exclude.lst deleted file mode 100644 index 060fca01d..000000000 --- a/deploy-exclude.lst +++ /dev/null @@ -1,13 +0,0 @@ -*__pycache__* -.git/* -app/assets/* -bower_components/* -cache/* -.cache/* -node_modules/* -target/* -venv/* -build/* -.envrc -tests/.cache/* -.cf/* From 56329daa9a741b4c887dab29b777fa045f12b4a3 Mon Sep 17 00:00:00 2001 From: stvnrlly Date: Wed, 26 Oct 2022 15:45:45 +0000 Subject: [PATCH 11/19] more docs --- README.md | 47 +++++++++++++++++++----------- docs/api-usage.md | 10 +++++++ docs/database-management.md | 4 +++ docs/infra-onboarding.md | 7 ----- docs/infra-overview.md | 57 +++++++++++++++++++++++++++++++++++++ docs/infra-setup.md | 20 ------------- 6 files changed, 101 insertions(+), 44 deletions(-) create mode 100644 docs/api-usage.md delete mode 100644 docs/infra-onboarding.md create mode 100644 docs/infra-overview.md delete mode 100644 docs/infra-setup.md diff --git a/README.md b/README.md index 1bd8ed0c4..73877fa92 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,36 @@ This repo contains: - An internal-only REST API built using Flask to manage services, users, templates, etc., which the [admin UI](http://github.com/18F/notifications-admin) talks to) - Asynchronous workers built using Celery to put things on queues and read them off to be processed, sent to providers, updated, etc. +Our other repositories are: + +- [notifications-admin](https://github.com/GSA/notifications-admin) +- [notifications-utils](https://github.com/GSA/notifications-utils) +- [us-notify-compliance](https://github.com/GSA/us-notify-compliance/) +- [notify-python-demo](https://github.com/GSA/notify-python-demo) + +## Documentation, here and elsewhere + +### About Notify + +- [Roadmap](https://notifications-admin.app.cloud.gov/features/roadmap) +- [Using the API](./docs/api-usage.md) + +### Infrastructure + +- [Overview, setup, and onboarding](./docs/infra-overview.md) +- [Database management](./docs/database-management.md) + +### Common dev work + +- [Local setup](#local-setup) +- [Testing](./docs/testing.md) +- [Running one-off tasks](./docs/one-off-tasks.md) + +## UK docs that may still be helpful + +- [Writing public APIs](docs/writing-public-apis.md) +- [Updating dependencies](https://github.com/alphagov/notifications-manuals/wiki/Dependencies) + ## Local setup ### Direct installation @@ -73,20 +103,3 @@ If you're working in VS Code, you can also leverage Docker for a containerized d NOTE: when you change .env in the future, you'll need to rebuild the devcontainer for the change to take effect. Vscode _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. -## Deeper documentation - -### Infrastructure - -- [Checklist for onboarding to all of the things](./docs/infra-onboarding.md) -- [Setting up the initial infrastructure using AWS](./docs/infra-setup.md) -- [Database management](./docs/database-management.md) - -### Common dev work - -- [Testing](./docs/testing.md) -- [Running one-off tasks](./docs/one-off-tasks.md) - -## UK docs that may still be helpful - -- [Writing public APIs](docs/writing-public-apis.md) -- [Updating dependencies](https://github.com/alphagov/notifications-manuals/wiki/Dependencies) diff --git a/docs/api-usage.md b/docs/api-usage.md new file mode 100644 index 000000000..8ec2b4e94 --- /dev/null +++ b/docs/api-usage.md @@ -0,0 +1,10 @@ +# API Usage + +## Connecting to the API + +To make life easier, the [UK API client libraries](https://www.notifications.service.gov.uk/documentation) are compatible with Notify. + +For a usage example, see [our Python demo](https://github.com/GSA/notify-python-demo). + +An API key can be created at https://notifications-admin.app.cloud.gov/services/YOUR_SERVICE_ID/api/keys. However, in order to successfully send messages, you will need to receive a secret header token from the Notify team. + diff --git a/docs/database-management.md b/docs/database-management.md index 589df97fd..9d8685ce7 100644 --- a/docs/database-management.md +++ b/docs/database-management.md @@ -12,6 +12,10 @@ about what is loaded into which tables, and some plans for how we might manage t Flask does not seem to have a great way to squash migrations, but rather wants you to recreate them from the DB structure. This means it's easy to recreate the tables, but hard to recreate the initial data. +## Data Model Diagram + +A diagram of Notify's data model is available [in our compliance repo](https://github.com/GSA/us-notify-compliance/blob/main/diagrams/rendered/apps/data.logical.pdf). + ## Migrations Create a migration: diff --git a/docs/infra-onboarding.md b/docs/infra-onboarding.md deleted file mode 100644 index 6d7789dfe..000000000 --- a/docs/infra-onboarding.md +++ /dev/null @@ -1,7 +0,0 @@ -# Infrastructure onboarding - -- [ ] Join [the GSA GitHub org](https://github.com/GSA/GitHub-Administration#join-the-gsa-organization) -- [ ] Get permissions for the repos -- [ ] Get access to the cloud.gov org && space -- [ ] Get access to AWS, if necessary -- [ ] Pull down creds from cloud.gov and create the local .env file \ No newline at end of file diff --git a/docs/infra-overview.md b/docs/infra-overview.md new file mode 100644 index 000000000..db017f8f3 --- /dev/null +++ b/docs/infra-overview.md @@ -0,0 +1,57 @@ +# Infrastructure overview + +A diagram of the system is available [in our compliance repo](https://github.com/GSA/us-notify-compliance/blob/main/diagrams/rendered/apps/application.boundary.png). + +Notify is a Flask application running on [cloud.gov](https://cloud.gov), which also brokers access to a PostgreSQL database and Redis store. + +In addition to the Flask app, Notify uses Celery to manage the task queue. Celery stores tasks in Redis. + +## Terraform + +The cloud.gov environment is configured with Terraform. See [the `terraform` folder](../terraform/) to learn about that. + +## AWS + +In addition to services provisioned through cloud.gov, we have several services provisioned directly in AWS. Our AWS services are currently located in the us-west-2 region using the tts-sandbox account. We plan to move to GovCloud shortly. + +To send messages, we use Amazon Web Services SNS and SES. In addition, we use AWS Pinpoint to provision and manage phone numbers, short codes, and long codes for sending SMS. + +In SES, we are currently using the "sandbox" mode. This requires email addresses to be pre-registered in the AWS console in order to receive emails. The DKIM settings live under the verified domain entry. + +In SNS, we have 3 topics for SMS receipts. These are not currently functional, so senders won't know the status of messages. + +Through Pinpoint, the API needs at least one number so that the application itself can send SMS for authentication codes. + +The API also has access to AWS S3 buckets for storing CSVs of messages and contact lists. It does not access a third S3 bucket that stores agency logos. + +We may be able to provision these services through cloud.gov, as well. In addition to [s3 support](https://cloud.gov/docs/services/s3/), there is [an SES brokerpak](https://github.com/GSA-TTS/datagov-brokerpak-smtp) and work on an SNS brokerpak. + +## Onboarding + +- [ ] Join [the GSA GitHub org](https://github.com/GSA/GitHub-Administration#join-the-gsa-organization) +- [ ] Get permissions for the repos +- [ ] Get access to the cloud.gov org && space +- [ ] Get [access to AWS](https://handbook.tts.gsa.gov/launching-software/infrastructure/#cloud-service-provider-csp-sandbox-accounts), if necessary +- [ ] Pull down creds from cloud.gov and create the local .env file +- [ ] Do stuff! + +## Setting up the infrastructure + +### Steps to prepare SES + +1. Go to SES console for \$AWS_REGION and create new origin and destination emails. AWS will send a verification via email which you'll need to complete. +2. Find and replace instances in the repo of "testsender", "testreceiver" and "dispostable.com", with your origin and destination email addresses, which you verified in step 1 above. + +TODO: create env vars for these origin and destination email addresses for the root service, and create new migrations to update postgres seed fixtures + +### Steps to prepare SNS + +1. Go to Pinpoints console for \$AWS_PINPOINT_REGION and choose "create new project", then "configure for sms" +2. Tick the box at the top to enable SMS, choose "transactional" as the default type and save +3. In the lefthand sidebar, go the "SMS and Voice" (bottom) and choose "Phone Numbers" +4. Under "Number Settings" choose "Request Phone Number" +5. Choose Toll-free number, tick SMS, untick Voice, choose "transactional", hit next and then "request" +6. Go to SNS console for \$AWS_PINPOINT_REGION, look at lefthand sidebar under "Mobile" and go to "Text Messaging (SMS)" +7. Scroll down to "Sandbox destination phone numbers" and tap "Add phone number" then follow the steps to verify (you'll need to be able to retrieve a code sent to each number) + +At this point, you _should_ be able to complete both the email and phone verification steps of the Notify user sign up process! 🎉 \ No newline at end of file diff --git a/docs/infra-setup.md b/docs/infra-setup.md deleted file mode 100644 index 9bd83f058..000000000 --- a/docs/infra-setup.md +++ /dev/null @@ -1,20 +0,0 @@ -# Setting up the infrastructure - -## Steps to prepare SES - -1. Go to SES console for \$AWS_REGION and create new origin and destination emails. AWS will send a verification via email which you'll need to complete. -2. Find and replace instances in the repo of "testsender", "testreceiver" and "dispostable.com", with your origin and destination email addresses, which you verified in step 1 above. - -TODO: create env vars for these origin and destination email addresses for the root service, and create new migrations to update postgres seed fixtures - -## Steps to prepare SNS - -1. Go to Pinpoints console for \$AWS_PINPOINT_REGION and choose "create new project", then "configure for sms" -2. Tick the box at the top to enable SMS, choose "transactional" as the default type and save -3. In the lefthand sidebar, go the "SMS and Voice" (bottom) and choose "Phone Numbers" -4. Under "Number Settings" choose "Request Phone Number" -5. Choose Toll-free number, tick SMS, untick Voice, choose "transactional", hit next and then "request" -6. Go to SNS console for \$AWS_PINPOINT_REGION, look at lefthand sidebar under "Mobile" and go to "Text Messaging (SMS)" -7. Scroll down to "Sandbox destination phone numbers" and tap "Add phone number" then follow the steps to verify (you'll need to be able to retrieve a code sent to each number) - -At this point, you _should_ be able to complete both the email and phone verification steps of the Notify user sign up process! 🎉 \ No newline at end of file From 82c5608e0adfd4a6e3cdfcd91629894261b513be Mon Sep 17 00:00:00 2001 From: stvnrlly Date: Wed, 26 Oct 2022 15:26:53 -0400 Subject: [PATCH 12/19] github setup change for pipenv --- .github/actions/setup-project/action.yml | 3 +++ Makefile | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/actions/setup-project/action.yml b/.github/actions/setup-project/action.yml index 003d8bf6e..a6caa11e9 100644 --- a/.github/actions/setup-project/action.yml +++ b/.github/actions/setup-project/action.yml @@ -13,3 +13,6 @@ runs: uses: actions/setup-python@v3 with: python-version: "3.9" + - name: Install pipenv + shell: bash + run: pip install --upgrade pipenv diff --git a/Makefile b/Makefile index 7bc3da75c..e03c05e46 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ GIT_COMMIT ?= $(shell git rev-parse HEAD) .PHONY: bootstrap bootstrap: ## Set up everything to run the app - generate-version-file + make generate-version-file pipenv install ---dev createdb notification_api || true (pipenv run flask db upgrade) || true From 9b9465c74a88ba546ae25ac666e250f1360a7af9 Mon Sep 17 00:00:00 2001 From: stvnrlly Date: Wed, 26 Oct 2022 15:34:12 -0400 Subject: [PATCH 13/19] remove extra hyphen --- Makefile | 2 +- Pipfile.lock | 76 ++++------------------------------------------------ 2 files changed, 6 insertions(+), 72 deletions(-) diff --git a/Makefile b/Makefile index e03c05e46..9e69493cc 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ GIT_COMMIT ?= $(shell git rev-parse HEAD) .PHONY: bootstrap bootstrap: ## Set up everything to run the app make generate-version-file - pipenv install ---dev + pipenv install --dev createdb notification_api || true (pipenv run flask db upgrade) || true diff --git a/Pipfile.lock b/Pipfile.lock index 565227cdb..4c841e4d8 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,7 @@ { "_meta": { "hash": { -<<<<<<< Updated upstream - "sha256": "eca9a39871c8db3e82fb384c39afc89fdc3c145c87653f5090927e578618ce11" -======= "sha256": "ce99b649bae4b10b084aacc936b72e01957eb9354cbc420d684590133c2da004" ->>>>>>> Stashed changes }, "pipfile-spec": 6, "requires": { @@ -374,29 +370,10 @@ "version": "==0.4.0" }, "flask-sqlalchemy": { -<<<<<<< Updated upstream -======= "git": "https://github.com/pallets-eco/flask-sqlalchemy.git", ->>>>>>> Stashed changes - "hashes": [ - "sha256:2bda44b43e7cacb15d4e05ff3cc1f8bc97936cc464623424102bfc2c35e95912", - "sha256:f12c3d4cc5cc7fdcc148b9527ea05671718c3ea45d50c7e732cceb33f574b390" - ], -<<<<<<< Updated upstream - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", -======= "ref": "aa7a61a5357cf6f5dcc135d98c781192457aa6fa", ->>>>>>> Stashed changes "version": "==2.5.1" }, - "flask-sqlalchemy==2-5-1": { - "git": "https://github.com/pallets-eco/flask-sqlalchemy.git", - "ref": "aa7a61a5357cf6f5dcc135d98c781192457aa6fa" - }, - "flask-sqlalchemy==2.5.1": { - "git": "https://github.com/pallets-eco/flask-sqlalchemy.git", - "ref": "aa7a61a5357cf6f5dcc135d98c781192457aa6fa" - }, "fqdn": { "hashes": [ "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", @@ -494,8 +471,6 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==1.1.3.post0" }, -<<<<<<< Updated upstream -======= "gunicorn": { "extras": [ "eventlet" @@ -504,15 +479,6 @@ "ref": "1299ea9e967a61ae2edebe191082fd169b864c64", "version": "==20.1.0" }, ->>>>>>> Stashed changes - "gunicorn[eventlet]==20-1-0": { - "git": "https://github.com/benoitc/gunicorn.git", - "ref": "1299ea9e967a61ae2edebe191082fd169b864c64" - }, - "gunicorn[eventlet]==20.1.0": { - "git": "https://github.com/benoitc/gunicorn.git", - "ref": "1299ea9e967a61ae2edebe191082fd169b864c64" - }, "idna": { "hashes": [ "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", @@ -755,11 +721,7 @@ }, "notifications-utils": { "git": "https://github.com/GSA/notifications-utils.git", -<<<<<<< Updated upstream - "ref": "90c12da575f4e481452d4fcd2a594204b0c28249" -======= "ref": "2cdffe3fa2417b61ce3d714dc5a2d67de6632bdd" ->>>>>>> Stashed changes }, "orderedset": { "hashes": [ @@ -988,7 +950,7 @@ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.8.2" }, "python-dotenv": { @@ -1061,11 +1023,7 @@ "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" ], -<<<<<<< Updated upstream - "markers": "python_version >= '3.7' and python_full_version < '4.0.0'", -======= "markers": "python_version >= '3.7' and python_version < '4'", ->>>>>>> Stashed changes "version": "==2.28.1" }, "rfc3339-validator": { @@ -1087,11 +1045,7 @@ "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2", "sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9" ], -<<<<<<< Updated upstream - "markers": "python_version >= '3.5' and python_full_version < '4.0.0'", -======= "markers": "python_version >= '3.5' and python_version < '4'", ->>>>>>> Stashed changes "version": "==4.7.2" }, "s3transfer": { @@ -1160,7 +1114,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.16.0" }, "smartypants": { @@ -1246,11 +1200,7 @@ "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e", "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" ], -<<<<<<< Updated upstream - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_full_version < '4.0.0'", -======= "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'", ->>>>>>> Stashed changes "version": "==1.26.12" }, "vine": { @@ -1362,19 +1312,11 @@ }, "zipp": { "hashes": [ -<<<<<<< Updated upstream - "sha256:3a7af91c3db40ec72dd9d154ae18e008c69efe8ca88dde4f9a731bb82fe2f9eb", - "sha256:972cfa31bc2fedd3fa838a51e9bc7e64b7fb725a8c00e7431554311f180e9980" - ], - "markers": "python_version >= '3.7'", - "version": "==3.9.0" -======= "sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1", "sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8" ], "markers": "python_version >= '3.7'", "version": "==3.10.0" ->>>>>>> Stashed changes } }, "develop": { @@ -1814,7 +1756,7 @@ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.8.2" }, "pytz": { @@ -1864,11 +1806,7 @@ "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" ], -<<<<<<< Updated upstream - "markers": "python_version >= '3.7' and python_full_version < '4.0.0'", -======= "markers": "python_version >= '3.7' and python_version < '4'", ->>>>>>> Stashed changes "version": "==2.28.1" }, "requests-mock": { @@ -1900,7 +1838,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.16.0" }, "toml": { @@ -1908,7 +1846,7 @@ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==0.10.2" }, "tomli": { @@ -1931,11 +1869,7 @@ "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e", "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" ], -<<<<<<< Updated upstream - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_full_version < '4.0.0'", -======= "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'", ->>>>>>> Stashed changes "version": "==1.26.12" }, "werkzeug": { From 2889f6220a3084a9c2b4e8676dff0346bccd97b7 Mon Sep 17 00:00:00 2001 From: stvnrlly Date: Wed, 26 Oct 2022 16:21:45 -0400 Subject: [PATCH 14/19] actually write requirements to file --- .github/workflows/checks.yml | 2 +- .github/workflows/daily_checks.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 6fcdf8958..9dad4fbe5 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -72,7 +72,7 @@ jobs: - uses: actions/checkout@v3 - uses: ./.github/actions/setup-project - name: Create requirements.txt - run: pipenv requirements + run: pipenv requirements > requirements.txt - uses: trailofbits/gh-action-pip-audit@v1.0.0 with: inputs: requirements.txt diff --git a/.github/workflows/daily_checks.yml b/.github/workflows/daily_checks.yml index 1449ef3d5..d63917306 100644 --- a/.github/workflows/daily_checks.yml +++ b/.github/workflows/daily_checks.yml @@ -39,7 +39,7 @@ jobs: - uses: actions/checkout@v3 - uses: ./.github/actions/setup-project - name: Create requirements.txt - run: pipenv requirements + run: pipenv requirements > requirements.txt - uses: trailofbits/gh-action-pip-audit@v1.0.0 with: inputs: requirements.txt From 7b80210884f70c0aa6a62af7984a466436a8f808 Mon Sep 17 00:00:00 2001 From: stvnrlly Date: Wed, 26 Oct 2022 16:29:51 -0400 Subject: [PATCH 15/19] locate isort in time and space --- .github/workflows/checks.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 9dad4fbe5..7bf04a114 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -60,9 +60,9 @@ jobs: # - name: Run style checks # run: flake8 . - name: Check imports alphabetized - run: isort --check-only ./app ./tests + run: pipenv run isort --check-only ./app ./tests - name: Run tests - run: pytest -n4 --maxfail=10 + run: pipenv run pytest -n4 --maxfail=10 env: SQLALCHEMY_DATABASE_TEST_URI: postgresql://user:password@localhost:5432/test_notification_api From 96431f038867c49e96d1db22cbdde35c0890f0df Mon Sep 17 00:00:00 2001 From: stvnrlly Date: Wed, 26 Oct 2022 16:47:40 -0400 Subject: [PATCH 16/19] pipenv + flake8 --- .github/workflows/checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index b07714731..df553a7da 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -57,7 +57,7 @@ jobs: env: SQLALCHEMY_DATABASE_TEST_URI: postgresql://user:password@localhost:5432/test_notification_api - name: Run style checks - run: flake8 . + run: pipenv run flake8 . - name: Check imports alphabetized run: pipenv run isort --check-only ./app ./tests - name: Run tests From 493e7e015a8592a97e67207ef3346e363d32ed30 Mon Sep 17 00:00:00 2001 From: stvnrlly Date: Fri, 28 Oct 2022 12:58:07 +0000 Subject: [PATCH 17/19] pipenv in devcontainers, probably --- Makefile | 14 +- Pipfile | 2 + Pipfile.lock | 243 +++++++++++++++++- .../scripts/notify-dev-entrypoint.sh | 5 +- .../scripts/notify-worker-entrypoint.sh | 3 +- 5 files changed, 248 insertions(+), 19 deletions(-) diff --git a/Makefile b/Makefile index 3318739bf..b14f342fa 100644 --- a/Makefile +++ b/Makefile @@ -49,9 +49,9 @@ generate-version-file: ## Generates the app version file .PHONY: test test: ## Run tests - flake8 . - isort --check-only ./app ./tests - pytest -n4 --maxfail=10 + pipenv run flake8 . + pipenv run isort --check-only ./app ./tests + pipenv run pytest -n4 --maxfail=10 .PHONY: freeze-requirements freeze-requirements: ## Pin all requirements including sub dependencies into requirements.txt @@ -60,16 +60,14 @@ freeze-requirements: ## Pin all requirements including sub dependencies into req .PHONY: audit audit: - pip install --upgrade pip-audit pipenv requirements > requirements.txt pipenv requirements --dev > requirements_for_test.txt - 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: static-scan static-scan: - pip install bandit - bandit -r app/ + pipenv run bandit -r app/ .PHONY: clean clean: diff --git a/Pipfile b/Pipfile index c1ae3089f..dea8f5f5c 100644 --- a/Pipfile +++ b/Pipfile @@ -75,6 +75,8 @@ pytest-xdist = "==2.5.0" freezegun = "==1.2.1" requests-mock = "==1.9.3" jinja2-cli = {version = "==0.8.2", extras = ["yaml"]} +pip-audit = "*" +bandit = "*" [requires] python_version = "3.9" diff --git a/Pipfile.lock b/Pipfile.lock index 4c841e4d8..3cdd1fea4 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "ce99b649bae4b10b084aacc936b72e01957eb9354cbc420d684590133c2da004" + "sha256": "4d2f356b612e2a1c813e6d0f0db1d73948a7124b19ff012d993b74ec5cc4b03e" }, "pipfile-spec": 6, "requires": { @@ -950,7 +950,7 @@ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.8.2" }, "python-dotenv": { @@ -1114,7 +1114,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, "smartypants": { @@ -1328,6 +1328,14 @@ "index": "pypi", "version": "==21.4.0" }, + "bandit": { + "hashes": [ + "sha256:2d63a8c573417bae338962d4b9b06fbc6080f74ecd955a092849e1e65c717bd2", + "sha256:412d3f259dab4077d0e7f0c11f50f650cc7d10db905d98f6520a95a18049658a" + ], + "index": "pypi", + "version": "==1.7.4" + }, "boto3": { "hashes": [ "sha256:15733c2bbedce7a36fcf1749560c72c3ee90785aa6302a98658c7bffdcbe1f2a", @@ -1344,6 +1352,17 @@ "index": "pypi", "version": "==1.26.8" }, + "cachecontrol": { + "extras": [ + "filecache" + ], + "hashes": [ + "sha256:2c75d6a8938cb1933c75c50184549ad42728a27e9f6b92fd677c3151aa72555b", + "sha256:a5b9fcc986b184db101aa280b42ecdcdfc524892596f606858e0b7a8b4d9e144" + ], + "markers": "python_version >= '3.6'", + "version": "==0.12.11" + }, "certifi": { "hashes": [ "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7", @@ -1416,6 +1435,13 @@ "index": "pypi", "version": "==2.0.12" }, + "commonmark": { + "hashes": [ + "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60", + "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9" + ], + "version": "==0.9.1" + }, "coverage": { "extras": [ "toml" @@ -1507,6 +1533,14 @@ "markers": "python_version >= '3.6'", "version": "==38.0.1" }, + "cyclonedx-python-lib": { + "hashes": [ + "sha256:39e9d36347d4dc736474ab4f3a7cd7bc91050c9315df698f83a6d8bbcb290744", + "sha256:3c79f32bb7d6ed34eac3308dbc8f2a77fbd1fd3779991173a147d866eaa7423e" + ], + "markers": "python_version >= '3.6' and python_version < '4.0'", + "version": "==3.1.0" + }, "execnet": { "hashes": [ "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5", @@ -1539,6 +1573,30 @@ "index": "pypi", "version": "==1.2.1" }, + "gitdb": { + "hashes": [ + "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd", + "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa" + ], + "markers": "python_version >= '3.6'", + "version": "==4.0.9" + }, + "gitpython": { + "hashes": [ + "sha256:41eea0deec2deea139b459ac03656f0dd28fc4a3387240ec1d3c259a2c47850f", + "sha256:cc36bfc4a3f913e66805a28e84703e419d9c264c1077e537b54f0e1af85dbefd" + ], + "markers": "python_version >= '3.7'", + "version": "==3.1.29" + }, + "html5lib": { + "hashes": [ + "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", + "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.1" + }, "idna": { "hashes": [ "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", @@ -1588,6 +1646,13 @@ "markers": "python_version >= '3.7'", "version": "==1.0.1" }, + "lockfile": { + "hashes": [ + "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799", + "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa" + ], + "version": "==0.12.2" + }, "markupsafe": { "hashes": [ "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", @@ -1649,6 +1714,71 @@ "index": "pypi", "version": "==3.1.9" }, + "msgpack": { + "hashes": [ + "sha256:002b5c72b6cd9b4bafd790f364b8480e859b4712e91f43014fe01e4f957b8467", + "sha256:0a68d3ac0104e2d3510de90a1091720157c319ceeb90d74f7b5295a6bee51bae", + "sha256:0df96d6eaf45ceca04b3f3b4b111b86b33785683d682c655063ef8057d61fd92", + "sha256:0dfe3947db5fb9ce52aaea6ca28112a170db9eae75adf9339a1aec434dc954ef", + "sha256:0e3590f9fb9f7fbc36df366267870e77269c03172d086fa76bb4eba8b2b46624", + "sha256:11184bc7e56fd74c00ead4f9cc9a3091d62ecb96e97653add7a879a14b003227", + "sha256:112b0f93202d7c0fef0b7810d465fde23c746a2d482e1e2de2aafd2ce1492c88", + "sha256:1276e8f34e139aeff1c77a3cefb295598b504ac5314d32c8c3d54d24fadb94c9", + "sha256:1576bd97527a93c44fa856770197dec00d223b0b9f36ef03f65bac60197cedf8", + "sha256:1e91d641d2bfe91ba4c52039adc5bccf27c335356055825c7f88742c8bb900dd", + "sha256:26b8feaca40a90cbe031b03d82b2898bf560027160d3eae1423f4a67654ec5d6", + "sha256:2999623886c5c02deefe156e8f869c3b0aaeba14bfc50aa2486a0415178fce55", + "sha256:2a2df1b55a78eb5f5b7d2a4bb221cd8363913830145fad05374a80bf0877cb1e", + "sha256:2bb8cdf50dd623392fa75525cce44a65a12a00c98e1e37bf0fb08ddce2ff60d2", + "sha256:2cc5ca2712ac0003bcb625c96368fd08a0f86bbc1a5578802512d87bc592fe44", + "sha256:35bc0faa494b0f1d851fd29129b2575b2e26d41d177caacd4206d81502d4c6a6", + "sha256:3c11a48cf5e59026ad7cb0dc29e29a01b5a66a3e333dc11c04f7e991fc5510a9", + "sha256:449e57cc1ff18d3b444eb554e44613cffcccb32805d16726a5494038c3b93dab", + "sha256:462497af5fd4e0edbb1559c352ad84f6c577ffbbb708566a0abaaa84acd9f3ae", + "sha256:4733359808c56d5d7756628736061c432ded018e7a1dff2d35a02439043321aa", + "sha256:48f5d88c99f64c456413d74a975bd605a9b0526293218a3b77220a2c15458ba9", + "sha256:49565b0e3d7896d9ea71d9095df15b7f75a035c49be733051c34762ca95bbf7e", + "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250", + "sha256:4d5834a2a48965a349da1c5a79760d94a1a0172fbb5ab6b5b33cbf8447e109ce", + "sha256:4dea20515f660aa6b7e964433b1808d098dcfcabbebeaaad240d11f909298075", + "sha256:545e3cf0cf74f3e48b470f68ed19551ae6f9722814ea969305794645da091236", + "sha256:63e29d6e8c9ca22b21846234913c3466b7e4ee6e422f205a2988083de3b08cae", + "sha256:6916c78f33602ecf0509cc40379271ba0f9ab572b066bd4bdafd7434dee4bc6e", + "sha256:6a4192b1ab40f8dca3f2877b70e63799d95c62c068c84dc028b40a6cb03ccd0f", + "sha256:6c9566f2c39ccced0a38d37c26cc3570983b97833c365a6044edef3574a00c08", + "sha256:76ee788122de3a68a02ed6f3a16bbcd97bc7c2e39bd4d94be2f1821e7c4a64e6", + "sha256:7760f85956c415578c17edb39eed99f9181a48375b0d4a94076d84148cf67b2d", + "sha256:77ccd2af37f3db0ea59fb280fa2165bf1b096510ba9fe0cc2bf8fa92a22fdb43", + "sha256:81fc7ba725464651190b196f3cd848e8553d4d510114a954681fd0b9c479d7e1", + "sha256:85f279d88d8e833ec015650fd15ae5eddce0791e1e8a59165318f371158efec6", + "sha256:9667bdfdf523c40d2511f0e98a6c9d3603be6b371ae9a238b7ef2dc4e7a427b0", + "sha256:a75dfb03f8b06f4ab093dafe3ddcc2d633259e6c3f74bb1b01996f5d8aa5868c", + "sha256:ac5bd7901487c4a1dd51a8c58f2632b15d838d07ceedaa5e4c080f7190925bff", + "sha256:aca0f1644d6b5a73eb3e74d4d64d5d8c6c3d577e753a04c9e9c87d07692c58db", + "sha256:b17be2478b622939e39b816e0aa8242611cc8d3583d1cd8ec31b249f04623243", + "sha256:c1683841cd4fa45ac427c18854c3ec3cd9b681694caf5bff04edb9387602d661", + "sha256:c23080fdeec4716aede32b4e0ef7e213c7b1093eede9ee010949f2a418ced6ba", + "sha256:d5b5b962221fa2c5d3a7f8133f9abffc114fe218eb4365e40f17732ade576c8e", + "sha256:d603de2b8d2ea3f3bcb2efe286849aa7a81531abc52d8454da12f46235092bcb", + "sha256:e83f80a7fec1a62cf4e6c9a660e39c7f878f603737a0cdac8c13131d11d97f52", + "sha256:eb514ad14edf07a1dbe63761fd30f89ae79b42625731e1ccf5e1f1092950eaa6", + "sha256:eba96145051ccec0ec86611fe9cf693ce55f2a3ce89c06ed307de0e085730ec1", + "sha256:ed6f7b854a823ea44cf94919ba3f727e230da29feb4a99711433f25800cf747f", + "sha256:f0029245c51fd9473dc1aede1160b0a29f4a912e6b1dd353fa6d317085b219da", + "sha256:f5d869c18f030202eb412f08b28d2afeea553d6613aee89e200d7aca7ef01f5f", + "sha256:fb62ea4b62bfcb0b380d5680f9a4b3f9a2d166d9394e9bbd9666c0ee09a3645c", + "sha256:fcb8a47f43acc113e24e910399376f7277cf8508b27e5b88499f053de6b115a8" + ], + "version": "==1.0.4" + }, + "packageurl-python": { + "hashes": [ + "sha256:5c91334f942cd55d45eb0c67dd339a535ef90e25f05b9ec016ad188ed0ef9048", + "sha256:bf8a1ffe755634776f6563904d792fb0aa13b377fc86115c36fe17f69b6e59db" + ], + "markers": "python_version >= '3.6'", + "version": "==0.10.4" + }, "packaging": { "hashes": [ "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", @@ -1657,6 +1787,46 @@ "markers": "python_version >= '3.6'", "version": "==21.3" }, + "pbr": { + "hashes": [ + "sha256:b97bc6695b2aff02144133c2e7399d5885223d42b7912ffaec2ca3898e673bfe", + "sha256:db2317ff07c84c4c63648c9064a79fe9d9f5c7ce85a9099d4b6258b3db83225a" + ], + "markers": "python_version >= '2.6'", + "version": "==5.11.0" + }, + "pip": { + "hashes": [ + "sha256:1daab4b8d3b97d1d763caeb01a4640a2250a0ea899e257b1e44b9eded91e15ab", + "sha256:8182aec21dad6c0a49a2a3d121a87cd524b950e0b6092b181625f07ebdde7530" + ], + "markers": "python_version >= '3.7'", + "version": "==22.3" + }, + "pip-api": { + "hashes": [ + "sha256:2a0314bd31522eb9ffe8a99668b0d07fee34ebc537931e7b6483001dbedcbdc9", + "sha256:a05df2c7aa9b7157374bcf4273544201a0c7bae60a9c65bcf84f3959ef3896f3" + ], + "markers": "python_version >= '3.7'", + "version": "==0.0.30" + }, + "pip-audit": { + "hashes": [ + "sha256:a6205bb586f5964325b1af888914bf547d91f588a5f5b2c4d73f04a39fcc276f", + "sha256:cc7be2253f80dba44e8ae0002c26bf920114436b0097bfd483581c8b607caae2" + ], + "index": "pypi", + "version": "==2.4.4" + }, + "pip-requirements-parser": { + "hashes": [ + "sha256:22fa213a987913385b2484d5698ecfa1d9cf4154978cdf929085548af55355b0", + "sha256:8c2a6f8e091ac2693824a5ef4e3b250226e34f74a20a91a87b9ab0714b47788f" + ], + "markers": "python_version >= '3.6'", + "version": "==31.2.0" + }, "pluggy": { "hashes": [ "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", @@ -1696,6 +1866,14 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.4.0" }, + "pygments": { + "hashes": [ + "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1", + "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42" + ], + "markers": "python_version >= '3.6'", + "version": "==2.13.0" + }, "pyparsing": { "hashes": [ "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", @@ -1756,7 +1934,7 @@ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.8.2" }, "pytz": { @@ -1817,6 +1995,13 @@ "index": "pypi", "version": "==1.9.3" }, + "resolvelib": { + "hashes": [ + "sha256:c6ea56732e9fb6fca1b2acc2ccc68a0b6b8c566d8f3e78e0443310ede61dbd37", + "sha256:d9b7907f055c3b3a2cfc56c914ffd940122915826ff5fb5b1de0c99778f4de98" + ], + "version": "==0.8.1" + }, "responses": { "hashes": [ "sha256:396acb2a13d25297789a5866b4881cf4e46ffd49cc26c43ab1117f40b973102e", @@ -1825,6 +2010,14 @@ "markers": "python_version >= '3.7'", "version": "==0.22.0" }, + "rich": { + "hashes": [ + "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e", + "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0" + ], + "markers": "python_full_version >= '3.6.3' and python_full_version < '4.0.0'", + "version": "==12.6.0" + }, "s3transfer": { "hashes": [ "sha256:7a6f4c4d1fdb9a2b640244008e142cbc2cd3ae34b386584ef044dd0f27101971", @@ -1833,20 +2026,51 @@ "markers": "python_version >= '3.6'", "version": "==0.5.2" }, + "setuptools": { + "hashes": [ + "sha256:512e5536220e38146176efb833d4a62aa726b7bbff82cfbc8ba9eaa3996e0b17", + "sha256:f62ea9da9ed6289bfe868cd6845968a2c854d1427f8548d52cae02a42b4f0356" + ], + "markers": "python_version >= '3.7'", + "version": "==65.5.0" + }, "six": { "hashes": [ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, + "smmap": { + "hashes": [ + "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94", + "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936" + ], + "markers": "python_version >= '3.6'", + "version": "==5.0.0" + }, + "sortedcontainers": { + "hashes": [ + "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", + "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" + ], + "version": "==2.4.0" + }, + "stevedore": { + "hashes": [ + "sha256:02518a8f0d6d29be8a445b7f2ac63753ff29e8f2a2faa01777568d5500d777a6", + "sha256:3b1cbd592a87315f000d05164941ee5e164899f8fc0ce9a00bb0f321f40ef93e" + ], + "markers": "python_version >= '3.8'", + "version": "==4.1.0" + }, "toml": { "hashes": [ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.10.2" }, "tomli": { @@ -1872,6 +2096,13 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'", "version": "==1.26.12" }, + "webencodings": { + "hashes": [ + "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", + "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" + ], + "version": "==0.5.1" + }, "werkzeug": { "hashes": [ "sha256:1ce08e8093ed67d638d63879fd1ba3735817f7a80de3674d293f5984f25fb6e6", diff --git a/devcontainer-api/scripts/notify-dev-entrypoint.sh b/devcontainer-api/scripts/notify-dev-entrypoint.sh index bd7b7a30b..469bbfffe 100755 --- a/devcontainer-api/scripts/notify-dev-entrypoint.sh +++ b/devcontainer-api/scripts/notify-dev-entrypoint.sh @@ -30,14 +30,13 @@ cd /workspace git status make generate-version-file -pip3 install -r requirements.txt -pip3 install -r requirements_for_test.txt +pipenv install --dev # Install virtualenv to support running the isolated make freeze-requirements from within the devcontainer pip3 install virtualenv # Upgrade schema of the notification_api database -flask db upgrade +pipenv run flask db upgrade # Run flask server # make run-flask diff --git a/devcontainer-api/scripts/notify-worker-entrypoint.sh b/devcontainer-api/scripts/notify-worker-entrypoint.sh index 98251ef15..2af1696f1 100755 --- a/devcontainer-api/scripts/notify-worker-entrypoint.sh +++ b/devcontainer-api/scripts/notify-worker-entrypoint.sh @@ -30,8 +30,7 @@ cd /workspace git status make generate-version-file -pip3 install -r requirements.txt -pip3 install -r requirements_for_test.txt +pipenv install --dev # Install virtualenv to support running the isolated make freeze-requirements from within the devcontainer pip3 install virtualenv From 9b32bb55eac8216b418191b6048723961237e643 Mon Sep 17 00:00:00 2001 From: stvnrlly Date: Fri, 28 Oct 2022 11:11:17 -0400 Subject: [PATCH 18/19] more ci info --- README.md | 1 + docs/deploying.md | 18 ++++++++++++++++++ docs/testing.md | 2 ++ 3 files changed, 21 insertions(+) create mode 100644 docs/deploying.md diff --git a/README.md b/README.md index 73877fa92..16b93b3d3 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Our other repositories are: - [Local setup](#local-setup) - [Testing](./docs/testing.md) +- [Deploying](./docs/deploying.md) - [Running one-off tasks](./docs/one-off-tasks.md) ## UK docs that may still be helpful diff --git a/docs/deploying.md b/docs/deploying.md new file mode 100644 index 000000000..5fa129b01 --- /dev/null +++ b/docs/deploying.md @@ -0,0 +1,18 @@ +# Deploying + +We deploy automatically to cloud.gov for production and staging environments. + +Deployment runs via the [deployment action](../.github/workflows/deploy.yml) on GitHub, which pulls credentials from GitHub's secrets store. + +The [action that we use](https://github.com/18F/cg-deploy-action) deploys using [a rolling strategy](https://docs.cloudfoundry.org/devguide/deploy-apps/rolling-deploy.html), so all deployments should have zero downtime. + +The API has 2 deployment environments: + +- Production, which deploys from `main` +- Staging, which does not, in fact, exist + +Configurations for these are located in [the `deploy-config` folder](../deploy-config/). + +In the event that a deployment includes a Terraform change, that change will run before any code is deployed to the environment. Each environment has its own Terraform GitHub Action to handle that change. + +Failures in any of these GitHub workflows will be surfaced in the Pull Request related to the code change, and in the case of `checks.yml` actively prevent the PR from being merged. Failure in the Terraform workflow will not actively prevent the PR from being merged, but reviewers should not approve a PR with a failing terraform plan. \ No newline at end of file diff --git a/docs/testing.md b/docs/testing.md index 2294c52cf..90c5bdb2d 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -21,6 +21,8 @@ On GitHub, in addition to these tests, we run: We're using GitHub Actions. See [/.github](../.github/) for the configuration. +In addition to commit-triggered scans, the `daily_checks.yml` workflow runs the relevant dependency audits, static scan, and/or dynamic scans at 10am UTC each day. Developers will be notified of failures in daily scans by GitHub notifications. + ## To run a local OWASP scan 1. Run `make run-flask` from within the dev container. From 64f8641013a84028a3177debf2790256e4bd0bfe Mon Sep 17 00:00:00 2001 From: stvnrlly Date: Fri, 28 Oct 2022 12:48:22 -0400 Subject: [PATCH 19/19] improvements from feedback --- README.md | 18 ++++++------------ docs/one-off-tasks.md | 2 +- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 16b93b3d3..79a682726 100644 --- a/README.md +++ b/README.md @@ -43,15 +43,13 @@ Our other repositories are: ### Direct installation -1. Set up Postgres && Redis +1. Set up Postgres && Redis on your machine + +1. Install [pipenv](https://pipenv.pypa.io/en/latest/) 1. Install dependencies into a virtual environment - ``` - pipenv install --dev - createdb notification_api - flask db upgrade - ``` + `make bootstrap` 1. Create the .env file @@ -62,15 +60,11 @@ Our other repositories are: 1. Run Flask - ``` - pipenv run make run-flask - ``` + `make run-flask` 1. Run Celery - ``` - pipenv run make run-celery - ``` + `make run-celery` ### VS Code && Docker installation diff --git a/docs/one-off-tasks.md b/docs/one-off-tasks.md index edbfcefea..a337eaf01 100644 --- a/docs/one-off-tasks.md +++ b/docs/one-off-tasks.md @@ -8,7 +8,7 @@ both with `pytest` and with trial runs. To run a command on cloud.gov, use this format: ``` -cf run-task CLOUD-GOV-SPACE --commmand "YOUR COMMAND HERE" --name YOUR-COMMAND +cf run-task CLOUD-GOV-APP --commmand "YOUR COMMAND HERE" --name YOUR-COMMAND ``` [Here's more documentation](https://docs.cloudfoundry.org/devguide/using-tasks.html) about Cloud Foundry tasks.