├── .flake8 ├── .github ├── pull_request_template.md └── workflows │ └── ci.yml ├── .gitignore ├── .nvmrc ├── LICENSE ├── Makefile ├── Pipfile ├── Pipfile.lock ├── README.md ├── cdk.json ├── cdk ├── __init__.py ├── app.py └── remote_workstation_stack.py ├── docker ├── Dockerfile └── docker-entrypoint.sh ├── example.env ├── images ├── remote-ssh-hosts.png ├── remote-ssh-icon.png ├── remote-ssh-index-html.png ├── remote-ssh-jupyter.png ├── remote-ssh-port-forwarding-prompt.png ├── remote-ssh-port-forwarding.png └── remote-ssh-setup.png ├── package-lock.json └── utils └── generate_ssh_config.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## What I am changing 2 | 3 | ## How I did it 4 | 5 | ## How you can test it 6 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | 8 | jobs: 9 | linting: 10 | runs-on: ubuntu-18.04 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Set up Python 3.8.8 15 | uses: actions/setup-python@v2 16 | with: 17 | python-version: 3.8.8 18 | 19 | - name: Install Pipenv 20 | uses: dschep/install-pipenv-action@v1 21 | 22 | - name: Get pipenv venv hashes 23 | id: hashes 24 | run: | 25 | echo "##[set-output name=root;]$(python -c 'import sys; import base64; import hashlib; print(base64.urlsafe_b64encode(hashlib.sha256(sys.argv[-1].encode()).digest()[:6]).decode()[:8])' $(pwd)/Pipfile)" 26 | 27 | - name: Setup root cache 28 | uses: actions/cache@v2 29 | id: root-cache 30 | with: 31 | path: /home/runner/.local/share/virtualenvs/remote-workstation-${{ steps.hashes.outputs.root }} 32 | key: root-${{ hashFiles('/home/runner/work/remote-workstation/remote-workstation/Pipfile.lock') }} 33 | 34 | - name: Install root dependencies 35 | if: steps.root-cache.outputs.cache-hit != 'true' 36 | run: | 37 | pipenv install --dev 38 | 39 | - name: Run linting 40 | run: | 41 | make lint 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/python,node,vscode 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=python,node,vscode 4 | 5 | ### Node ### 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # TypeScript v1 declaration files 50 | typings/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Optional stylelint cache 62 | .stylelintcache 63 | 64 | # Microbundle cache 65 | .rpt2_cache/ 66 | .rts2_cache_cjs/ 67 | .rts2_cache_es/ 68 | .rts2_cache_umd/ 69 | 70 | # Optional REPL history 71 | .node_repl_history 72 | 73 | # Output of 'npm pack' 74 | *.tgz 75 | 76 | # Yarn Integrity file 77 | .yarn-integrity 78 | 79 | # dotenv environment variables file 80 | .env 81 | .env.test 82 | .env*.local 83 | 84 | # parcel-bundler cache (https://parceljs.org/) 85 | .cache 86 | .parcel-cache 87 | 88 | # Next.js build output 89 | .next 90 | 91 | # Nuxt.js build / generate output 92 | .nuxt 93 | dist 94 | 95 | # Gatsby files 96 | .cache/ 97 | # Comment in the public line in if your project uses Gatsby and not Next.js 98 | # https://nextjs.org/blog/next-9-1#public-directory-support 99 | # public 100 | 101 | # vuepress build output 102 | .vuepress/dist 103 | 104 | # Serverless directories 105 | .serverless/ 106 | 107 | # FuseBox cache 108 | .fusebox/ 109 | 110 | # DynamoDB Local files 111 | .dynamodb/ 112 | 113 | # TernJS port file 114 | .tern-port 115 | 116 | # Stores VSCode versions used for testing VSCode extensions 117 | .vscode-test 118 | 119 | ### Python ### 120 | # Byte-compiled / optimized / DLL files 121 | __pycache__/ 122 | *.py[cod] 123 | *$py.class 124 | 125 | # C extensions 126 | *.so 127 | 128 | # Distribution / packaging 129 | .Python 130 | build/ 131 | develop-eggs/ 132 | dist/ 133 | downloads/ 134 | eggs/ 135 | .eggs/ 136 | lib/ 137 | lib64/ 138 | parts/ 139 | sdist/ 140 | var/ 141 | wheels/ 142 | pip-wheel-metadata/ 143 | share/python-wheels/ 144 | *.egg-info/ 145 | .installed.cfg 146 | *.egg 147 | MANIFEST 148 | 149 | # PyInstaller 150 | # Usually these files are written by a python script from a template 151 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 152 | *.manifest 153 | *.spec 154 | 155 | # Installer logs 156 | pip-log.txt 157 | pip-delete-this-directory.txt 158 | 159 | # Unit test / coverage reports 160 | htmlcov/ 161 | .tox/ 162 | .nox/ 163 | .coverage 164 | .coverage.* 165 | nosetests.xml 166 | coverage.xml 167 | *.cover 168 | *.py,cover 169 | .hypothesis/ 170 | .pytest_cache/ 171 | pytestdebug.log 172 | 173 | # Translations 174 | *.mo 175 | *.pot 176 | 177 | # Django stuff: 178 | local_settings.py 179 | db.sqlite3 180 | db.sqlite3-journal 181 | 182 | # Flask stuff: 183 | instance/ 184 | .webassets-cache 185 | 186 | # Scrapy stuff: 187 | .scrapy 188 | 189 | # Sphinx documentation 190 | docs/_build/ 191 | doc/_build/ 192 | 193 | # PyBuilder 194 | target/ 195 | 196 | # Jupyter Notebook 197 | .ipynb_checkpoints 198 | 199 | # IPython 200 | profile_default/ 201 | ipython_config.py 202 | 203 | # pyenv 204 | .python-version 205 | 206 | # pipenv 207 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 208 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 209 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 210 | # install all needed dependencies. 211 | #Pipfile.lock 212 | 213 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 214 | __pypackages__/ 215 | 216 | # Celery stuff 217 | celerybeat-schedule 218 | celerybeat.pid 219 | 220 | # SageMath parsed files 221 | *.sage.py 222 | 223 | # Environments 224 | .venv 225 | env/ 226 | venv/ 227 | ENV/ 228 | env.bak/ 229 | venv.bak/ 230 | pythonenv* 231 | 232 | # Spyder project settings 233 | .spyderproject 234 | .spyproject 235 | 236 | # Rope project settings 237 | .ropeproject 238 | 239 | # mkdocs documentation 240 | /site 241 | 242 | # mypy 243 | .mypy_cache/ 244 | .dmypy.json 245 | dmypy.json 246 | 247 | # Pyre type checker 248 | .pyre/ 249 | 250 | # pytype static type analyzer 251 | .pytype/ 252 | 253 | # profiling data 254 | .prof 255 | 256 | ### vscode ### 257 | .vscode/ 258 | !.vscode/settings.json 259 | !.vscode/tasks.json 260 | !.vscode/launch.json 261 | !.vscode/extensions.json 262 | *.code-workspace 263 | 264 | # End of https://www.toptal.com/developers/gitignore/api/python,node,vscode 265 | 266 | .env 267 | cdk.out 268 | 269 | # Created by https://www.toptal.com/developers/gitignore/api/ssh 270 | # Edit at https://www.toptal.com/developers/gitignore?templates=ssh 271 | 272 | ### SSH ### 273 | **/.ssh/id_* 274 | **/.ssh/*_id_* 275 | **/.ssh/known_hosts 276 | 277 | # End of https://www.toptal.com/developers/gitignore/api/ssh 278 | 279 | # Manually created SSH ignores 280 | 281 | **/.ssh 282 | **id_rsa* 283 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 14 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Development Seed 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONEY: install install-dev lint format diff deploy destroy ssh-config ssh-to-instance 2 | 3 | install: 4 | npm install 5 | pipenv install 6 | 7 | install-dev: 8 | npm install 9 | pipenv install --dev 10 | 11 | lint: 12 | pipenv run flake8 cdk/ utils/ 13 | pipenv run isort --check-only --profile black cdk/ utils/ 14 | pipenv run black --check --diff cdk/ utils/ 15 | 16 | format: 17 | pipenv run isort --profile black cdk/ utils/ 18 | pipenv run black cdk/ utils/ 19 | 20 | diff: 21 | pipenv run npx cdk diff --app cdk/app.py || true 22 | 23 | deploy: 24 | pipenv run npx cdk deploy --app cdk/app.py --require-approval never && pipenv run python3 utils/generate_ssh_config.py 25 | 26 | destroy: 27 | pipenv run npx cdk destroy --app cdk/app.py --force 28 | 29 | ssh-config: 30 | pipenv run python3 utils/generate_ssh_config.py 31 | 32 | ssh-to-instance: 33 | source .env && ssh -F $${SSH_CONFIG_LOCATION:-./.ssh/config} remote-workstation-$${IDENTIFIER:-dev} 34 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [packages] 7 | "aws-cdk.core" = "==1.95.1" 8 | "aws-cdk.aws-ec2" = "==1.95.1" 9 | "aws-cdk.aws-ecs" = "==1.95.1" 10 | "aws-cdk.aws-logs" = "==1.95.1" 11 | "aws-cdk.aws-ecr" = "==1.95.1" 12 | "aws-cdk.aws-ssm" = "==1.95.1" 13 | boto3 = "==1.17.39" 14 | requests = "==2.25.1" 15 | stringcase = "1.2.0" 16 | 17 | [dev-packages] 18 | flake8 = "==3.9.0" 19 | black = "==20.8b1" 20 | isort = "==5.8.0" 21 | 22 | [requires] 23 | python_version = "3.8" 24 | 25 | [pipenv] 26 | allow_prereleases = true 27 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "3d53c5e4efd4c7b486c39d50b5fd6a04a977947d975578999b99c9eb8e0099d8" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.8" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "attrs": { 20 | "hashes": [ 21 | "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", 22 | "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" 23 | ], 24 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 25 | "version": "==20.3.0" 26 | }, 27 | "aws-cdk.assets": { 28 | "hashes": [ 29 | "sha256:1731d69aa118c4af9d445a3d273dbd2243890fb5659a3bf194564269a118a2b6", 30 | "sha256:ecbea35b8a3254af8d91a35b8842f1e9aa5b5964fb32975330d14e518fdad519" 31 | ], 32 | "markers": "python_version >= '3.6'", 33 | "version": "==1.95.1" 34 | }, 35 | "aws-cdk.aws-apigateway": { 36 | "hashes": [ 37 | "sha256:31be51f799e9848806f703a889d399fa88f369b351206a92d6973ad9cb7f5910", 38 | "sha256:ee4e1db849e4b5ffa784653a9b424d6348eaabd91978c51dc762809691ba1a21" 39 | ], 40 | "markers": "python_version >= '3.6'", 41 | "version": "==1.95.1" 42 | }, 43 | "aws-cdk.aws-apigatewayv2": { 44 | "hashes": [ 45 | "sha256:20c9740c561a6ad6e393457b76255476b29d28aaad52487cc2257eee0a67dd4e", 46 | "sha256:556f8e6284ac158a767d9dc7883cd5c098fd00d3d4d3ddc874773b6fb02f924f" 47 | ], 48 | "markers": "python_version >= '3.6'", 49 | "version": "==1.95.1" 50 | }, 51 | "aws-cdk.aws-applicationautoscaling": { 52 | "hashes": [ 53 | "sha256:be643265c84a9ac89dfa8c091b4ad72fcd49b27755be8ad50cfbd7cffdb878ac", 54 | "sha256:ec277385584df08b1a14ed13b357d8a820608f86a02f431ee7138538819364ce" 55 | ], 56 | "markers": "python_version >= '3.6'", 57 | "version": "==1.95.1" 58 | }, 59 | "aws-cdk.aws-autoscaling": { 60 | "hashes": [ 61 | "sha256:083c5e1702ceb51ae9f628b21a784071113381935bcefd08cafefb57ce6208b0", 62 | "sha256:f8cfb1b52869fd2d765d4fe5c5a88372d710fae4ca21252a8ed9b184b0f5c9a7" 63 | ], 64 | "markers": "python_version >= '3.6'", 65 | "version": "==1.95.1" 66 | }, 67 | "aws-cdk.aws-autoscaling-common": { 68 | "hashes": [ 69 | "sha256:42b709f70f641bff1ab83a11d53532c0c92c1684e876ec9945b1d2fd26009d53", 70 | "sha256:ce7de41fd9b38ed060511be8bd052758984f587a5cf421d3c616e98abac46a69" 71 | ], 72 | "markers": "python_version >= '3.6'", 73 | "version": "==1.95.1" 74 | }, 75 | "aws-cdk.aws-autoscaling-hooktargets": { 76 | "hashes": [ 77 | "sha256:a7eb45b413a7b5f065dabdd0ae818bf24dc5a9c7b36a72c432c7c4b0c750848b", 78 | "sha256:cbf42556f6bde1594cb5867822671e1d1bf63a263acdb16f3ff2dc4474c4fe2e" 79 | ], 80 | "markers": "python_version >= '3.6'", 81 | "version": "==1.95.1" 82 | }, 83 | "aws-cdk.aws-certificatemanager": { 84 | "hashes": [ 85 | "sha256:173be2c9f33863f743831d3b255601a97ef092f01a911e325a896eb5a99dee83", 86 | "sha256:a3a4e9c1abd2476d274aef127e0766dc76fc639fa8a360f93bd5de65208af681" 87 | ], 88 | "markers": "python_version >= '3.6'", 89 | "version": "==1.95.1" 90 | }, 91 | "aws-cdk.aws-cloudformation": { 92 | "hashes": [ 93 | "sha256:1fa1387cd27a1cb279825bf77406acb4d9517c1c579e6850ba6bff160f6bd323", 94 | "sha256:8e752f446e4d85eb9696fdde5ef0167f181b787d06454d4dfc4b526b01860851" 95 | ], 96 | "markers": "python_version >= '3.6'", 97 | "version": "==1.95.1" 98 | }, 99 | "aws-cdk.aws-cloudfront": { 100 | "hashes": [ 101 | "sha256:432ba14e731881e5e9c635d0395f89545b921d6401178436c89b76141211897d", 102 | "sha256:73515e6b8e3d267081e02899d3ebe286a980955488d2a0f5b2f1dbd9e3887679" 103 | ], 104 | "markers": "python_version >= '3.6'", 105 | "version": "==1.95.1" 106 | }, 107 | "aws-cdk.aws-cloudwatch": { 108 | "hashes": [ 109 | "sha256:314a2ebb0bfe418243fc68e891d0def3d9122eacc7b680e6c5bc904e76472743", 110 | "sha256:dadb452e3cf2843cce37ab577d914c064b5a65927b338180fc608e0cbe93291f" 111 | ], 112 | "markers": "python_version >= '3.6'", 113 | "version": "==1.95.1" 114 | }, 115 | "aws-cdk.aws-codeguruprofiler": { 116 | "hashes": [ 117 | "sha256:89eb728fe42b9d25e5c2e56fe10e2b332142e474bb2f690d1b006338cd200a53", 118 | "sha256:ca7ea65a121ddc709471cc1cec978546af7b1417a6a5937152864840b0d6d88d" 119 | ], 120 | "markers": "python_version >= '3.6'", 121 | "version": "==1.95.1" 122 | }, 123 | "aws-cdk.aws-cognito": { 124 | "hashes": [ 125 | "sha256:71fdae0e6281c835ebb8b38197883ee6bd7ca1fb8c7fc67ba537afcdeddf86eb", 126 | "sha256:8b5fbcfed78963239073007031452c2fd8197d8fda01b74266347cc7974c22be" 127 | ], 128 | "markers": "python_version >= '3.6'", 129 | "version": "==1.95.1" 130 | }, 131 | "aws-cdk.aws-ec2": { 132 | "hashes": [ 133 | "sha256:84ff62cdcf22376b51d001ee2bc98f5cc59161a8e3057ba25319ecee317546a5", 134 | "sha256:be5e9fb3236c1cd989e5683c698246e8bab5753474173a8c96049a43d4c7cbd0" 135 | ], 136 | "index": "pypi", 137 | "version": "==1.95.1" 138 | }, 139 | "aws-cdk.aws-ecr": { 140 | "hashes": [ 141 | "sha256:269567428cd58c7bf0b2dc63057883afd189c5007875a5e2b1e2c460f9fa96c4", 142 | "sha256:9b73f6b839a95190289af5a940da79b98a29d52bf31c7515a00539a3004c449a" 143 | ], 144 | "index": "pypi", 145 | "version": "==1.95.1" 146 | }, 147 | "aws-cdk.aws-ecr-assets": { 148 | "hashes": [ 149 | "sha256:62a38af3e03c32eb3e0b0593ac0bd65aa2219117336d2e474403098f02d8da32", 150 | "sha256:d94015b4841bb4243d7725392bde36a43e118110f736afd4928b8bd0e499d55b" 151 | ], 152 | "markers": "python_version >= '3.6'", 153 | "version": "==1.95.1" 154 | }, 155 | "aws-cdk.aws-ecs": { 156 | "hashes": [ 157 | "sha256:198607271811f92a7f329bf9f39aaeb9afc4e592ffb77b73bb12d435192b1b01", 158 | "sha256:caa16662c5a2627d4507a28f8bd0e5cd42f694ef78aec14b86d3eeecde89e6dc" 159 | ], 160 | "index": "pypi", 161 | "version": "==1.95.1" 162 | }, 163 | "aws-cdk.aws-efs": { 164 | "hashes": [ 165 | "sha256:6681cc89959753be19438220bf1ecf026867cd824c9a1bce4af307bf54fdec30", 166 | "sha256:8b724bc1ed2c1ee5ae677d6865311f7052ce412894be1fb23daab6afc27152ac" 167 | ], 168 | "markers": "python_version >= '3.6'", 169 | "version": "==1.95.1" 170 | }, 171 | "aws-cdk.aws-elasticloadbalancing": { 172 | "hashes": [ 173 | "sha256:12f0271d4e3c499f7cf83ec506500be9459959162d1dbb8df17428aeffab4893", 174 | "sha256:9e22e5995910c1e5c3c33f6f13011e9a11ba484e08f33bf4112ba7805fc9e666" 175 | ], 176 | "markers": "python_version >= '3.6'", 177 | "version": "==1.95.1" 178 | }, 179 | "aws-cdk.aws-elasticloadbalancingv2": { 180 | "hashes": [ 181 | "sha256:43e2b072cf0f11613cf779c4183ce462d84d970c5c41c0c553a28a08629887dd", 182 | "sha256:831c172bad0a71551cb2eeeacde7cc249ab675bd9ac7c66bcadf0f2d8dfa7e0a" 183 | ], 184 | "markers": "python_version >= '3.6'", 185 | "version": "==1.95.1" 186 | }, 187 | "aws-cdk.aws-events": { 188 | "hashes": [ 189 | "sha256:8a332ce3545dae642af737de280fc5eac69b58042e21ce942b9445b72c4f939d", 190 | "sha256:a26950a7bffdbde8a8a3cd680cb03251ae82b32cb0811c879d00da9d6138a77a" 191 | ], 192 | "markers": "python_version >= '3.6'", 193 | "version": "==1.95.1" 194 | }, 195 | "aws-cdk.aws-globalaccelerator": { 196 | "hashes": [ 197 | "sha256:c0d6896c0ea6cafefb0defc7e32469137b27156c5b19822492b36124a7dc7aa0", 198 | "sha256:e3272bb3fd816f3a6cb464897295a36d4a6b7bf7b9001c10bd975aceeeb7d1a3" 199 | ], 200 | "markers": "python_version >= '3.6'", 201 | "version": "==1.95.1" 202 | }, 203 | "aws-cdk.aws-iam": { 204 | "hashes": [ 205 | "sha256:4360c1e5aaef1b80bf1fd6e4e4718529e91878ee9e01577f4d9051536ac94b11", 206 | "sha256:543dd13b5b96b549616e60c9e127b7a0522147d3b0d4ec3c277e331d8720a926" 207 | ], 208 | "markers": "python_version >= '3.6'", 209 | "version": "==1.95.1" 210 | }, 211 | "aws-cdk.aws-kms": { 212 | "hashes": [ 213 | "sha256:65205a551774b641e179e930992368c09768b2d1096544cfa45ec02282225a77", 214 | "sha256:dd4a0f4fd13d7f6cd1ff3ce3eec4bcaf71e7e9abec70983421e856391d6ae9bd" 215 | ], 216 | "markers": "python_version >= '3.6'", 217 | "version": "==1.95.1" 218 | }, 219 | "aws-cdk.aws-lambda": { 220 | "hashes": [ 221 | "sha256:6e8032f143b1773431d514d47a887b322e61249fab4e868133c321ddfaf89871", 222 | "sha256:c10e96e7480b13cea511158c6294213b2cceaa3ea9e3fb3c6af4c9cafa960511" 223 | ], 224 | "markers": "python_version >= '3.6'", 225 | "version": "==1.95.1" 226 | }, 227 | "aws-cdk.aws-logs": { 228 | "hashes": [ 229 | "sha256:5fd4fabc2bfd784e4b923abce842e745ad711e004a6d1a55a83641473e01ee94", 230 | "sha256:7a2dadb609a32d008f1dfc73341de5d72cb30035705d3d6acafe09a5af0d852c" 231 | ], 232 | "index": "pypi", 233 | "version": "==1.95.1" 234 | }, 235 | "aws-cdk.aws-route53": { 236 | "hashes": [ 237 | "sha256:a1f4755bbfcb8767999ba968d9879162968265474dd4e4e543749f89f685d542", 238 | "sha256:ecb15b4ee585a1b7dee60f873960af1dca92fd47f3301a6378209e69dd3a21ac" 239 | ], 240 | "markers": "python_version >= '3.6'", 241 | "version": "==1.95.1" 242 | }, 243 | "aws-cdk.aws-route53-targets": { 244 | "hashes": [ 245 | "sha256:3c1191e074df2e63573ae330a79fc94f04690cb7f183dc70ea591ec06d6a204e", 246 | "sha256:f566540cdab16f55d0639302d5124ffda00491f830824951e9b688a51df35ec4" 247 | ], 248 | "markers": "python_version >= '3.6'", 249 | "version": "==1.95.1" 250 | }, 251 | "aws-cdk.aws-s3": { 252 | "hashes": [ 253 | "sha256:32b69bcdba5d137b9cb3ccf637d1da170e7413d5409395f027eaa17bfe06a8cc", 254 | "sha256:b315c131d8fac43382396e011428ed02d2a68ae61755981bb7a82b73913f3fa5" 255 | ], 256 | "markers": "python_version >= '3.6'", 257 | "version": "==1.95.1" 258 | }, 259 | "aws-cdk.aws-s3-assets": { 260 | "hashes": [ 261 | "sha256:212c7e1670a02bb574e2801669cf8b1840ff699a3b15fb1b9aaef36e5bfb44c5", 262 | "sha256:df673334b09bc554d971ab3ea9b02db6bef7a19709ea354950b25e8eb869e792" 263 | ], 264 | "markers": "python_version >= '3.6'", 265 | "version": "==1.95.1" 266 | }, 267 | "aws-cdk.aws-sam": { 268 | "hashes": [ 269 | "sha256:06e133629b816010ecca0b56676de37a3f2098d09ea2c2f466c44b4b52b57192", 270 | "sha256:165013dd2da1d3032a1f5a3c310145828b4d0d8001ea1a82738de1ffc3bb091c" 271 | ], 272 | "markers": "python_version >= '3.6'", 273 | "version": "==1.95.1" 274 | }, 275 | "aws-cdk.aws-secretsmanager": { 276 | "hashes": [ 277 | "sha256:56b671f38c25b814a12fedca1dfc313e31c42ed5c7324d9aa3b086bfa2f93a5a", 278 | "sha256:5fde26652a558c57ffc6c3dfb7f0659c578357f799c33b1d901c0962369858cf" 279 | ], 280 | "markers": "python_version >= '3.6'", 281 | "version": "==1.95.1" 282 | }, 283 | "aws-cdk.aws-servicediscovery": { 284 | "hashes": [ 285 | "sha256:71bc449d7164316ca75264b09524a2076735e97a694d4da97df6beefee31e077", 286 | "sha256:a534133f00eea10b4b980484217b5b9eb8e49978724ad8927015f36b4f67fea8" 287 | ], 288 | "markers": "python_version >= '3.6'", 289 | "version": "==1.95.1" 290 | }, 291 | "aws-cdk.aws-signer": { 292 | "hashes": [ 293 | "sha256:03da07e788ca92e4bb221cd74b3231959273ea5ab92155c88ebcbe379ba77b23", 294 | "sha256:d0516a78c671cacc5543e202daab13d0e1e6b0ae698c10ab00012b0dcf8d3980" 295 | ], 296 | "markers": "python_version >= '3.6'", 297 | "version": "==1.95.1" 298 | }, 299 | "aws-cdk.aws-sns": { 300 | "hashes": [ 301 | "sha256:1a8cb7ab67f2fe686e937e3cf950de2f36b49678b5e9d2df0578d4f307f49663", 302 | "sha256:f2b660e9228ea645313d7c626b6db96fde3e8dec84fb34f2f60b777e6ce5b893" 303 | ], 304 | "markers": "python_version >= '3.6'", 305 | "version": "==1.95.1" 306 | }, 307 | "aws-cdk.aws-sns-subscriptions": { 308 | "hashes": [ 309 | "sha256:37ac0b1a374314f50c9413e63c6e19e11c5e04d0ef414f2af43e8dfe2f1933e5", 310 | "sha256:c122d2bd7ba6fbc9d2d622ad48fd2c4841d68cce51876e628e4da27f2bc938d0" 311 | ], 312 | "markers": "python_version >= '3.6'", 313 | "version": "==1.95.1" 314 | }, 315 | "aws-cdk.aws-sqs": { 316 | "hashes": [ 317 | "sha256:237a09a425d01867649a14689ff4c09e5e056ec67285c16209d16f98e4a40185", 318 | "sha256:dfc477adda0f95ba8e1b4b3bd7b2c03ca9b29f48efddac33bca5418b91637c2f" 319 | ], 320 | "markers": "python_version >= '3.6'", 321 | "version": "==1.95.1" 322 | }, 323 | "aws-cdk.aws-ssm": { 324 | "hashes": [ 325 | "sha256:87307a73c5250339980fa7247bb742f36add9e0138f6bd8af5ad191964f1fea2", 326 | "sha256:a5194964d60bab8f179bed68d0c97a51bd4e43b4a3d1db4b29762414b5f1f856" 327 | ], 328 | "index": "pypi", 329 | "version": "==1.95.1" 330 | }, 331 | "aws-cdk.cloud-assembly-schema": { 332 | "hashes": [ 333 | "sha256:3278c9d0d50be4db3887264f81c95aef02baffd464644c39e04eaf93d51ca40c", 334 | "sha256:94871d220823bc7f2c73c10f2b0afa216c5f7ba392b34d0ebcb6070b8362bd12" 335 | ], 336 | "markers": "python_version >= '3.6'", 337 | "version": "==1.95.1" 338 | }, 339 | "aws-cdk.core": { 340 | "hashes": [ 341 | "sha256:0120054706c34f35d70b8e6ffe96004bb2bc25fb983c982c2cdae598a64b0d8d", 342 | "sha256:9d05181bd93091728ae63334c8496ff7fad0d0723cd28b63a93d1a16967033b4" 343 | ], 344 | "index": "pypi", 345 | "version": "==1.95.1" 346 | }, 347 | "aws-cdk.custom-resources": { 348 | "hashes": [ 349 | "sha256:024682ea66ee69c4d81955f9c39ffad3a0230cd657db63c0287d51c4daac22c9", 350 | "sha256:4f3de6822c2d6599e7736216fd351af425b5305e0a4221f8ab0d0d6862b3e520" 351 | ], 352 | "markers": "python_version >= '3.6'", 353 | "version": "==1.95.1" 354 | }, 355 | "aws-cdk.cx-api": { 356 | "hashes": [ 357 | "sha256:77797cce0349cb88ce6de157426c1c62ed5611eb9587e2f756da8ce56460ecb7", 358 | "sha256:b8d79ef7b8abd74f9e4d923e55db454522d4d71450f7ee69650a8809a74835bd" 359 | ], 360 | "markers": "python_version >= '3.6'", 361 | "version": "==1.95.1" 362 | }, 363 | "aws-cdk.region-info": { 364 | "hashes": [ 365 | "sha256:48bdaccf5af91d4d7fa8f57c566d8271f0bad1bc2a32bc8373739f158eb8d2f7", 366 | "sha256:931db46e491eb06a24f869dd0ca5d25308087bfa07b3baafc0d886ac5259749f" 367 | ], 368 | "markers": "python_version >= '3.6'", 369 | "version": "==1.95.1" 370 | }, 371 | "boto3": { 372 | "hashes": [ 373 | "sha256:6ec718f5a75724f6117a47944a3b2dd79aef02ed75b356060cede74fb91e2616", 374 | "sha256:b5814ff73b5b8fc8601c1b73b70675807f9ce64713562e183a08415a2516eed4" 375 | ], 376 | "index": "pypi", 377 | "version": "==1.17.39" 378 | }, 379 | "botocore": { 380 | "hashes": [ 381 | "sha256:28506d23ffa9abf5666c2c909c7edc83a1112cd44fe74eb1a4960df561531e98", 382 | "sha256:54587d3c9d0d98ac579681245ea36f547cd5048e2bb9212e5e7166a963bcb562" 383 | ], 384 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 385 | "version": "==1.20.39" 386 | }, 387 | "cattrs": { 388 | "hashes": [ 389 | "sha256:12688f56fbb7f54cf647d031669840e1ab0b9a198bf374a217fcb5be821855df", 390 | "sha256:f92ca39ccb7373289f9cccf71b86849a29a2d75370bc983e7bf579ce95bfccd8" 391 | ], 392 | "markers": "python_version >= '3.7'", 393 | "version": "==1.3.0" 394 | }, 395 | "certifi": { 396 | "hashes": [ 397 | "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", 398 | "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" 399 | ], 400 | "version": "==2020.12.5" 401 | }, 402 | "chardet": { 403 | "hashes": [ 404 | "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", 405 | "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" 406 | ], 407 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 408 | "version": "==4.0.0" 409 | }, 410 | "constructs": { 411 | "hashes": [ 412 | "sha256:75e965ec695df76b6f1c1b6246879610f914e7f8ebf012279ef2c8c35d3c4352", 413 | "sha256:784a400e4eae93567d79b982e16db9af5cd04b8a242767b143ad59a4136888ad" 414 | ], 415 | "markers": "python_version >= '3.6'", 416 | "version": "==3.3.71" 417 | }, 418 | "idna": { 419 | "hashes": [ 420 | "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", 421 | "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" 422 | ], 423 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 424 | "version": "==2.10" 425 | }, 426 | "jmespath": { 427 | "hashes": [ 428 | "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9", 429 | "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f" 430 | ], 431 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 432 | "version": "==0.10.0" 433 | }, 434 | "jsii": { 435 | "hashes": [ 436 | "sha256:1f574354803448b6e55ee1f454b7e4a7dfc03d13ce09e760f9982ee3d048d8f4", 437 | "sha256:693c86eaa62bac26a29d918eb1095475fcd5f619e279b2ceba3ff99a1ecb02a3" 438 | ], 439 | "markers": "python_version ~= '3.6'", 440 | "version": "==1.26.0" 441 | }, 442 | "publication": { 443 | "hashes": [ 444 | "sha256:0248885351febc11d8a1098d5c8e3ab2dabcf3e8c0c96db1e17ecd12b53afbe6", 445 | "sha256:68416a0de76dddcdd2930d1c8ef853a743cc96c82416c4e4d3b5d901c6276dc4" 446 | ], 447 | "version": "==0.0.3" 448 | }, 449 | "python-dateutil": { 450 | "hashes": [ 451 | "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", 452 | "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" 453 | ], 454 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 455 | "version": "==2.8.1" 456 | }, 457 | "requests": { 458 | "hashes": [ 459 | "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", 460 | "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" 461 | ], 462 | "index": "pypi", 463 | "version": "==2.25.1" 464 | }, 465 | "s3transfer": { 466 | "hashes": [ 467 | "sha256:5d48b1fd2232141a9d5fb279709117aaba506cacea7f86f11bc392f06bfa8fc2", 468 | "sha256:c5dadf598762899d8cfaecf68eba649cd25b0ce93b6c954b156aaa3eed160547" 469 | ], 470 | "version": "==0.3.6" 471 | }, 472 | "six": { 473 | "hashes": [ 474 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", 475 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" 476 | ], 477 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 478 | "version": "==1.15.0" 479 | }, 480 | "stringcase": { 481 | "hashes": [ 482 | "sha256:48a06980661908efe8d9d34eab2b6c13aefa2163b3ced26972902e3bdfd87008" 483 | ], 484 | "index": "pypi", 485 | "version": "==1.2.0" 486 | }, 487 | "typing-extensions": { 488 | "hashes": [ 489 | "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", 490 | "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", 491 | "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" 492 | ], 493 | "version": "==3.7.4.3" 494 | }, 495 | "urllib3": { 496 | "hashes": [ 497 | "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df", 498 | "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937" 499 | ], 500 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", 501 | "version": "==1.26.4" 502 | } 503 | }, 504 | "develop": { 505 | "appdirs": { 506 | "hashes": [ 507 | "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", 508 | "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" 509 | ], 510 | "version": "==1.4.4" 511 | }, 512 | "black": { 513 | "hashes": [ 514 | "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea" 515 | ], 516 | "index": "pypi", 517 | "version": "==20.8b1" 518 | }, 519 | "click": { 520 | "hashes": [ 521 | "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", 522 | "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" 523 | ], 524 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 525 | "version": "==7.1.2" 526 | }, 527 | "flake8": { 528 | "hashes": [ 529 | "sha256:12d05ab02614b6aee8df7c36b97d1a3b2372761222b19b58621355e82acddcff", 530 | "sha256:78873e372b12b093da7b5e5ed302e8ad9e988b38b063b61ad937f26ca58fc5f0" 531 | ], 532 | "index": "pypi", 533 | "version": "==3.9.0" 534 | }, 535 | "isort": { 536 | "hashes": [ 537 | "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6", 538 | "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d" 539 | ], 540 | "index": "pypi", 541 | "version": "==5.8.0" 542 | }, 543 | "mccabe": { 544 | "hashes": [ 545 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", 546 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" 547 | ], 548 | "version": "==0.6.1" 549 | }, 550 | "mypy-extensions": { 551 | "hashes": [ 552 | "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", 553 | "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" 554 | ], 555 | "version": "==0.4.3" 556 | }, 557 | "pathspec": { 558 | "hashes": [ 559 | "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd", 560 | "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d" 561 | ], 562 | "version": "==0.8.1" 563 | }, 564 | "pycodestyle": { 565 | "hashes": [ 566 | "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068", 567 | "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef" 568 | ], 569 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 570 | "version": "==2.7.0" 571 | }, 572 | "pyflakes": { 573 | "hashes": [ 574 | "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3", 575 | "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db" 576 | ], 577 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 578 | "version": "==2.3.1" 579 | }, 580 | "regex": { 581 | "hashes": [ 582 | "sha256:07ef35301b4484bce843831e7039a84e19d8d33b3f8b2f9aab86c376813d0139", 583 | "sha256:13f50969028e81765ed2a1c5fcfdc246c245cf8d47986d5172e82ab1a0c42ee5", 584 | "sha256:14de88eda0976020528efc92d0a1f8830e2fb0de2ae6005a6fc4e062553031fa", 585 | "sha256:159fac1a4731409c830d32913f13f68346d6b8e39650ed5d704a9ce2f9ef9cb3", 586 | "sha256:18e25e0afe1cf0f62781a150c1454b2113785401ba285c745acf10c8ca8917df", 587 | "sha256:201e2619a77b21a7780580ab7b5ce43835e242d3e20fef50f66a8df0542e437f", 588 | "sha256:360a01b5fa2ad35b3113ae0c07fb544ad180603fa3b1f074f52d98c1096fa15e", 589 | "sha256:39c44532d0e4f1639a89e52355b949573e1e2c5116106a395642cbbae0ff9bcd", 590 | "sha256:3d9356add82cff75413bec360c1eca3e58db4a9f5dafa1f19650958a81e3249d", 591 | "sha256:3d9a7e215e02bd7646a91fb8bcba30bc55fd42a719d6b35cf80e5bae31d9134e", 592 | "sha256:4651f839dbde0816798e698626af6a2469eee6d9964824bb5386091255a1694f", 593 | "sha256:486a5f8e11e1f5bbfcad87f7c7745eb14796642323e7e1829a331f87a713daaa", 594 | "sha256:4b8a1fb724904139149a43e172850f35aa6ea97fb0545244dc0b805e0154ed68", 595 | "sha256:4c0788010a93ace8a174d73e7c6c9d3e6e3b7ad99a453c8ee8c975ddd9965643", 596 | "sha256:4c2e364491406b7888c2ad4428245fc56c327e34a5dfe58fd40df272b3c3dab3", 597 | "sha256:575a832e09d237ae5fedb825a7a5bc6a116090dd57d6417d4f3b75121c73e3be", 598 | "sha256:5770a51180d85ea468234bc7987f5597803a4c3d7463e7323322fe4a1b181578", 599 | "sha256:633497504e2a485a70a3268d4fc403fe3063a50a50eed1039083e9471ad0101c", 600 | "sha256:63f3ca8451e5ff7133ffbec9eda641aeab2001be1a01878990f6c87e3c44b9d5", 601 | "sha256:709f65bb2fa9825f09892617d01246002097f8f9b6dde8d1bb4083cf554701ba", 602 | "sha256:808404898e9a765e4058bf3d7607d0629000e0a14a6782ccbb089296b76fa8fe", 603 | "sha256:882f53afe31ef0425b405a3f601c0009b44206ea7f55ee1c606aad3cc213a52c", 604 | "sha256:8bd4f91f3fb1c9b1380d6894bd5b4a519409135bec14c0c80151e58394a4e88a", 605 | "sha256:8e65e3e4c6feadf6770e2ad89ad3deb524bcb03d8dc679f381d0568c024e0deb", 606 | "sha256:976a54d44fd043d958a69b18705a910a8376196c6b6ee5f2596ffc11bff4420d", 607 | "sha256:a0d04128e005142260de3733591ddf476e4902c0c23c1af237d9acf3c96e1b38", 608 | "sha256:a0df9a0ad2aad49ea3c7f65edd2ffb3d5c59589b85992a6006354f6fb109bb18", 609 | "sha256:a2ee026f4156789df8644d23ef423e6194fad0bc53575534101bb1de5d67e8ce", 610 | "sha256:a59a2ee329b3de764b21495d78c92ab00b4ea79acef0f7ae8c1067f773570afa", 611 | "sha256:b97ec5d299c10d96617cc851b2e0f81ba5d9d6248413cd374ef7f3a8871ee4a6", 612 | "sha256:b98bc9db003f1079caf07b610377ed1ac2e2c11acc2bea4892e28cc5b509d8d5", 613 | "sha256:b9d8d286c53fe0cbc6d20bf3d583cabcd1499d89034524e3b94c93a5ab85ca90", 614 | "sha256:bcd945175c29a672f13fce13a11893556cd440e37c1b643d6eeab1988c8b209c", 615 | "sha256:c66221e947d7207457f8b6f42b12f613b09efa9669f65a587a2a71f6a0e4d106", 616 | "sha256:c782da0e45aff131f0bed6e66fbcfa589ff2862fc719b83a88640daa01a5aff7", 617 | "sha256:cb4ee827857a5ad9b8ae34d3c8cc51151cb4a3fe082c12ec20ec73e63cc7c6f0", 618 | "sha256:d47d359545b0ccad29d572ecd52c9da945de7cd6cf9c0cfcb0269f76d3555689", 619 | "sha256:dc9963aacb7da5177e40874585d7407c0f93fb9d7518ec58b86e562f633f36cd", 620 | "sha256:ea2f41445852c660ba7c3ebf7d70b3779b20d9ca8ba54485a17740db49f46932", 621 | "sha256:f5d0c921c99297354cecc5a416ee4280bd3f20fd81b9fb671ca6be71499c3fdf", 622 | "sha256:f85d6f41e34f6a2d1607e312820971872944f1661a73d33e1e82d35ea3305e14" 623 | ], 624 | "version": "==2021.3.17" 625 | }, 626 | "toml": { 627 | "hashes": [ 628 | "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", 629 | "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" 630 | ], 631 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 632 | "version": "==0.10.2" 633 | }, 634 | "typed-ast": { 635 | "hashes": [ 636 | "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1", 637 | "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d", 638 | "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6", 639 | "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd", 640 | "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37", 641 | "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151", 642 | "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07", 643 | "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440", 644 | "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70", 645 | "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496", 646 | "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea", 647 | "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400", 648 | "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc", 649 | "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606", 650 | "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc", 651 | "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581", 652 | "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412", 653 | "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a", 654 | "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2", 655 | "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787", 656 | "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f", 657 | "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937", 658 | "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64", 659 | "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487", 660 | "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b", 661 | "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41", 662 | "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a", 663 | "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3", 664 | "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166", 665 | "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10" 666 | ], 667 | "version": "==1.4.2" 668 | }, 669 | "typing-extensions": { 670 | "hashes": [ 671 | "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", 672 | "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", 673 | "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" 674 | ], 675 | "version": "==3.7.4.3" 676 | } 677 | } 678 | } 679 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Remote Workstation ☁️🌎📦 ![CI Status](https://github.com/developmentseed/remote-workstation/actions/workflows/ci.yml/badge.svg) 2 | 3 | 4 | This project aims to enable the deployment of a dockerised workstation that can be SSH'd into 5 | 6 | * [Dependencies](#dependencies) 7 | * [General usage](#general-usage) 8 | * [With VS Code Remote SSH](#with-vs-code-remote-ssh) 9 | * [How this all works](#how-this-all-works) 10 | * [Customising the container image](#customising-the-container-image) 11 | * [Make commands](#make-commands) 12 | * [References](#references) 13 | 14 | # Dependencies 15 | 16 | To be able to deploy your own workstation, you will need some prerequisites installed: 17 | 18 | * Node 14 (We recommend using NVM [Node Version Manager](https://github.com/nvm-sh/nvm)) 19 | * [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html) - There is a `package.json` in the repository, it's recommended to run `npm install` in the repository root and make use of `npx ` rather than globally installing AWS CDK 20 | * Python 3.8.* (We recommend using [pyenv](https://github.com/pyenv/pyenv)) 21 | * [pipenv](https://github.com/pypa/pipenv) 22 | * [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html) 23 | * [Docker](https://docs.docker.com/get-docker/) 24 | 25 | If you're developing on MacOS, all of the above (apart from AWS CDK) can be installed using [homebrew](https://brew.sh/) 26 | 27 | If you're developing on Windows, we'd recommend using either [Git BASH](https://gitforwindows.org/) or [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10) 28 | # General usage 29 | 30 | ## 1. Install dependencies 31 | 32 | If you only intend on deploying the infrastructure _as is_, you can install only the dependencies required for deployment with: 33 | 34 | ```bash 35 | $ make install 36 | ``` 37 | 38 | However, if you intend on developing on this project and making contributions, you can install _all_ deployment and development dependencies with: 39 | 40 | ```bash 41 | $ make install-dev 42 | ``` 43 | 44 | ## 2. Create an SSH keypair 45 | 46 | ```bash 47 | $ ssh-keygen -b 2048 -t rsa -f -q -N "" 48 | ``` 49 | 50 | ## 3. Prepare .env file 51 | 52 | To perform some actions, this project requires a `.env` file to be present in the base of the project with some variables present. 53 | 54 | An example `.env` file is provided: `example.env`, copy and rename this to `.env` and populate it with your own values. 55 | 56 | Below is a table explaining the values we expect (and that can be used additionally) in your `.env` file: 57 | 58 | 59 | | Variable | Value(s) | Required | Default | Description | 60 | |------------------------------- |---------------------------------------------------------------------------------- |---------- |--------------------------------------------------------------------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 61 | | `SSH_PRIVATE_KEY_LOCATION` | `` | ✅ | N/A | You created this in Step 3 | 62 | | `SSH_PUBLIC_KEY_LOCATION` | `` | ✅ | N/A | You created this in Step 3 | 63 | | `AWS_PROFILE` | `` | 🚫 | default | The AWS Profile to deploy to | 64 | | `IDENTIFIER` | `` | 🚫 | dev | This is unique to your deployed stack - If a conflict occurs, your deployment will fail | 65 | | `SSH_CONFIG_LOCATION` | `` | 🚫 | No value will result in a .ssh/config file created in the repo root | The SSH Config file to add the remote workstations details to | 66 | | `INSTANCE_CPU` | `` | 🚫 | 256 | See container CPU & Memory mappings [here](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_ecs/FargateTaskDefinition.html#aws_cdk.aws_ecs.FargateTaskDefinition) | 67 | | `INSTANCE_MEMORY` | `` | 🚫 | 512 | See container CPU & Memory mappings [here](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_ecs/FargateTaskDefinition.html#aws_cdk.aws_ecs.FargateTaskDefinition) | 68 | | `CONTAINER_ECR_REPOSITORY` | `` | 🚫 | N/A | The name of an ECR repository in the region and account you're deploying into - **Note**: See [Customising the container image](#customising-the-container-image) | 69 | | `CONTAINER_DOCKER_REPOSITORY` | `` | 🚫 | N/A | Must be public - Credentials are currently not supported within this project - **Note** : See [Customising the container image](#customising-the-container-image) | 70 | | `CONTAINER_LOCAL_PATH` | `` | 🚫 | N/A | The file used to build the image must be called Dockerfile - **Note** : See [Customising the container image](#customising-the-container-image) | 71 | | `TAGS_` | `` | 🚫 | N/A | You can add as many tags as AWS allows. To add a tag, add an entry to your `.env` file like `TAGS_MY_COOL_TAG="thisiscool"` - Your AWS tag will be named with a Pascal case name like: `MyCoolTag` with the value you provided. You can read more about tags for AWS billing and tracking infrastructure [here](https://docs.aws.amazon.com/general/latest/gr/aws_tagging.html)| 72 | 73 | ## 4. Deploy the instance 74 | 75 | ```bash 76 | $ make deploy 77 | ``` 78 | 79 | ## 5. SSH to your instance 80 | 81 | ```bash 82 | $ make ssh-to-instance 83 | ``` 84 | 85 | # With VS Code Remote SSH 86 | 87 | VS Code has an extension called [VS Code Remote SSH - Available here](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh) - With this extension you can SSH onto a machine and develop on it in VS Code (locally). It also provides other utitilies such as SSH Port Forwarding. 88 | 89 | To use the extension, install it and then make sure you've done Steps 1-4 under [General usage](#General-usage) 90 | 91 | ## 1. Find Remote SSH command 92 | 93 | With the plugin enabled, either open the actions menu in VS Code (MacOS is `CMD + Shift + P`), or select the little icon to the bottom left, selecting `Remote-SSH: Connect to Host...` 94 | 95 | ![Remote SSH icon in VS Code](./images/remote-ssh-icon.png) 96 | 97 | ## 2. Connect to host 98 | 99 | You should now see a prompt with the contents of your SSH Config file (listing the hosts), you should see `remote-workstation-` - select that. 100 | 101 | ![Remote SSH hosts](./images/remote-ssh-hosts.png) 102 | 103 | _**Note:** You might not see your workstation here, this is usually the case if you A: do not have an SSH Host config file in the default location or B: didn't provide one and the deployment has generated you one in `.ssh/config` in the root of the repository._ 104 | 105 | _You can remedy this by selecting `Configure SSH Hosts...` and then `Settings` and providing the path to the non standard location_ 106 | 107 | _The setting name is `remote.SSH.configFile`, if you'd rather search for it_ 108 | 109 | ## 3. Pointers 110 | 111 | ### Files 112 | Now that you have established a connection to the instance, you can open files/folders on it like you would normally in VS Code: `File -> Open...` 113 | 114 | ### Extensions 115 | Because VS Code Remote SSH bootstraps an VS Code server on the running instance, we can install any VS Code extension inside it, allowing us access to language support, enhanced debugging, and various other features. 116 | 117 | ### Port Forwarding 118 | If you're running a service that you'd like to access via `localhost` - say a webapp or a Jupyter Notebook, VS Code Remote SSH comes with SSH Port Forwarding built in. 119 | 120 | 121 | #### Simple example 122 | 123 | A very easy example you can peform on the `docker/Dockerfile` image is: 124 | ```bash 125 | # On the instance 126 | $ echo "Hello Remote Workspace!" > index.html 127 | $ python -m http.server 8181 128 | ``` 129 | 130 | When you then look at the Remote SSH pane, you'll see `Ports` which shows you which ports have services running on them in the instance, you can then select them to forward them on 131 | 132 | ![Remote SSH Port Forwarding](./images/remote-ssh-port-forwarding.png) 133 | 134 | Then you can access it on `localhost:`: 135 | 136 | ![Remote SSH Port Forwarding index.html](./images/remote-ssh-index-html.png) 137 | 138 | _**Note:** you might also notice a prompt appear in VS Code asking if you'd like to visit the forwarded application, providing you a link to click on:_ 139 | 140 | ![Remote SSH Port Forwarding prompt](./images/remote-ssh-port-forwarding-prompt.png) 141 | 142 | #### Jupyter Example 143 | 144 | If your image has Jupyter Notebook installed (or if you run `pip3 install jupyter` on the provided `docker/Dockerfile`), you 145 | can setup port forwarding for a notebook instance: 146 | ```bash 147 | # On the instance 148 | $ jupyter-notebook --no-browser --port= 149 | ``` 150 | 151 | Similarly to the [Simple Example](#simple-example), you will see the port you provided appear in the `Ports` selection, you can then forward it, navigate to `localhost:` and access your notebook! 152 | 153 | ![Remote SSH Port Forwarding Jupyter Notebook](./images/remote-ssh-jupyter.png) 154 | 155 | # How this all works 156 | 157 | This project revolves around the ability to add a SSH client to a Docker container that we then serve via [AWS Fargate](https://aws.amazon.com/fargate/) 158 | 159 | Below is a high level diagram of what is happening architecturally: 160 | 161 | ![High level architecture](./images/remote-ssh-setup.png) 162 | 163 | The user runs `make deploy`, under the hood this does: 164 | 165 | 1. A CDK deploy which deploys a Fargate instance. This Fargate service is based on a docker image within `docker/`. As part of this, the users public IP is added to a security group to allow SSH access to the instance. The public key the user provided is also added into the instance 166 | 2. The `utils/generate_ssh_config.py` script is called. This first identifies the public IP address of the Fargate instance, then generates an SSH config entry for the host. If a location is provided for the config file, the entry is either added or overwritten. If the location is not provided, the `.ssh/config` file is created and the entry is added 167 | 3. Finally (external to `make deploy`), the user SSH's onto the Fargate instance 168 | 169 | # Customising the container image 170 | 171 | ## The image 172 | 173 | To use this project, the only essential components to ensure a Docker image has are: 174 | 175 | * An SSH server ([`openssh-server`](https://ubuntu.com/server/docs/service-openssh)) 176 | * `curl` 177 | * An entrypoint script (like `docker/docker-entrypoint.sh`) that takes the public key and adds it to the authorised keys area. It should also start the SSH daemon 178 | 179 | From then on, whatever you do with the container is up to you (Setting up users, workspaces, dependencies, etc.) 180 | 181 | You can remove the `Dockerfile` that is currently in the `docker/` directory and add your own, just make sure it does the same setup steps 182 | 183 | If you have a Docker Image or a `Dockerfile` elsewhere external to this project, we use a heirarchy of values from `.env` to get which image to use, the order is from top to bottom: 184 | 185 | * `CONTAINER_ECR_REPOSITORY` - Highest priority 186 | * `CONTAINER_DOCKER_REPOSITORY` 187 | * `CONTAINER_LOCAL_PATH` 188 | * None of the above - uses `docker/` in this repository 189 | 190 | ## The instance 191 | 192 | By default, the project deploys a container with `0.25 vCPU` and `0.5GB RAM` - These can be altered by using `INSTANCE_CPU` and `INSTANCE_MEMORY` in your `.env` file. **NOTE**: CPU and Memory are tied, you can get more information about these mappings [here](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_ecs/FargateTaskDefinition.html#aws_cdk.aws_ecs.FargateTaskDefinition) 193 | 194 | # Make commands 195 | 196 | A `Makefile` is provided to abstract commonly used commands away for ease of use, a breakdown of the commands is: 197 | 198 | **`make install`** 199 | 200 | > This will run `npm install` and `pipenv install` on the repo root, installing only the dependencies needed for a production deployment 201 | 202 | **`make install-dev`** 203 | 204 | > This will run `npm install` and `pipenv install --dev` on the repo root, installing the dependencies needed for development of this project 205 | 206 | **`make lint`** 207 | 208 | > This will perform a dry run of `flake8`, `isort`, and `black` and let you know what issues were found 209 | 210 | **`make format`** 211 | 212 | > This will peform a run of `isort` and `black`, this **will** modify files if issues were found 213 | 214 | **`make diff`** 215 | 216 | > This will run a `cdk diff` using the contents of your `.env` file 217 | 218 | **`make deploy`** 219 | 220 | > This will run a `cdk deploy` using the contents of your `.env` file. The deployment is auto-approved, so **make sure** you know what you're changing with your deployment first! (Best to run `make diff` to check!) 221 | 222 | **`make destroy`** 223 | 224 | > This will run a `cdk destroy` using the contents of your `.env` file. The destroy is auto-approved, so **make sure** you know what you're destroying first! 225 | 226 | **`make ssh-config`** 227 | 228 | > This will generate the SSH config entry (only really useful if for some reason the Fargate instance is re-created) 229 | 230 | **`make ssh-to-instance`** 231 | 232 | > This will SSH to the instance, if the values of `SSH_CONFIG_LOCATION` and `IDENTIFIER` are not present in `.env`, the default values of `./.ssh/config` and `dev` will be used respectively 233 | 234 | # References 235 | 236 | * [VS Code Remote SSH](https://code.visualstudio.com/docs/remote/ssh) 237 | * [9 Steps to SSH into an AWS Fargate managed container](https://medium.com/ci-t/9-steps-to-ssh-into-an-aws-fargate-managed-container-46c1d5f834e2) 238 | * [How do I retrieve the public IP for a fargate task using the CLI?](https://stackoverflow.com/questions/49354116/how-do-i-retrieve-the-public-ip-for-a-fargate-task-using-the-cli) 239 | -------------------------------------------------------------------------------- /cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "python3 app.py", 3 | "context": { 4 | "@aws-cdk/core:enableStackNameDuplicates": "true", 5 | "aws-cdk:enableDiffNoFail": "true", 6 | "@aws-cdk/core:stackRelativeExports": "true", 7 | "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /cdk/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/remote-workstation/897444dabda078ad7ec57f5d60cd40f41f80cc72/cdk/__init__.py -------------------------------------------------------------------------------- /cdk/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | 5 | import requests 6 | from aws_cdk import core 7 | from remote_workstation_stack import RemoteWorkstationStack 8 | from stringcase import pascalcase 9 | 10 | 11 | def get_public_ip() -> str: 12 | return requests.get("https://api.ipify.org?format=json").json()["ip"] 13 | 14 | 15 | app = core.App() 16 | 17 | identifier = os.environ.get("IDENTIFIER", "dev") 18 | RemoteWorkstationStack( 19 | app, 20 | f"remote-workstation-{identifier}", 21 | identifier=identifier, 22 | public_ip=get_public_ip(), 23 | ) 24 | 25 | tags = [ 26 | (pascalcase(item[0].replace("TAGS_", "").lower()), item[1]) 27 | for item in os.environ.items() 28 | if item[0].startswith("TAGS_") 29 | ] 30 | 31 | for k, v in tags: 32 | core.Tags.of(app).add(k, v, apply_to_launched_instances=True) 33 | 34 | app.synth() 35 | -------------------------------------------------------------------------------- /cdk/remote_workstation_stack.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from aws_cdk import aws_ec2 as ec2 4 | from aws_cdk import aws_ecr as ecr 5 | from aws_cdk import aws_ecs as ecs 6 | from aws_cdk import aws_logs as logs 7 | from aws_cdk import core 8 | 9 | 10 | class RemoteWorkstationStack(core.Stack): 11 | def __init__( 12 | self, 13 | scope: core.Construct, 14 | construct_id: str, 15 | identifier: str, 16 | public_ip: str, 17 | **kwargs, 18 | ) -> None: 19 | super().__init__(scope, construct_id, **kwargs) 20 | 21 | vpc = ec2.Vpc( 22 | self, 23 | f"vpc-{identifier}", 24 | max_azs=1, 25 | subnet_configuration=[ 26 | ec2.SubnetConfiguration( 27 | name=f"public-subnet-{identifier}", 28 | subnet_type=ec2.SubnetType.PUBLIC, 29 | cidr_mask=28, 30 | ) 31 | ], 32 | ) 33 | 34 | self.cluster = ecs.Cluster( 35 | self, 36 | f"cluster-{identifier}", 37 | vpc=vpc, 38 | cluster_name=f"remote-cluster-{identifier}", 39 | ) 40 | 41 | task_definition = ecs.FargateTaskDefinition( 42 | self, 43 | f"fargate-task-definition-{identifier}", 44 | cpu=int(os.environ.get("INSTANCE_CPU", 256)), 45 | memory_limit_mib=int(os.environ.get("INSTANCE_MEMORY", 512)), 46 | ) 47 | 48 | log_driver = ecs.AwsLogDriver( 49 | stream_prefix=f"remote-workstation/{identifier}", 50 | log_retention=logs.RetentionDays.ONE_WEEK, 51 | ) 52 | 53 | task_definition.add_container( 54 | f"container-definition-{identifier}", 55 | image=self.get_docker_image(identifier), 56 | logging=log_driver, 57 | environment={"SSH_PUBLIC_KEY": self.get_ssh_public_key()}, 58 | ) 59 | 60 | fargate_service = ecs.FargateService( 61 | self, 62 | f"fargate-service-{identifier}", 63 | assign_public_ip=True, 64 | cluster=self.cluster, 65 | desired_count=1, 66 | task_definition=task_definition, 67 | propagate_tags=ecs.PropagatedTagSource.SERVICE, 68 | ) 69 | 70 | for security_group in fargate_service.connections.security_groups: 71 | security_group.add_ingress_rule( 72 | peer=ec2.Peer.ipv4(f"{public_ip}/32"), 73 | connection=ec2.Port.tcp(22), 74 | description=f"SSH Access from {identifier}s Public IP", 75 | ) 76 | 77 | def get_ssh_public_key(self) -> str: 78 | with open(os.environ["SSH_PUBLIC_KEY_LOCATION"], "r") as reader: 79 | return reader.readline() 80 | 81 | def get_docker_image(self, identifier: str) -> ecs.AssetImage: 82 | if ecr_repo := os.environ.get("CONTAINER_ECR_REPOSITORY", None): 83 | return ecs.ContainerImage.from_ecr_repository( 84 | ecr.Repository.from_repository_name( 85 | self, id=f"ecr-repository-{identifier}", repository_name=ecr_repo 86 | ) 87 | ) 88 | elif docker_repo := os.environ.get("CONTAINER_DOCKER_REPOSITORY", None): 89 | return ecs.ContainerImage.from_registry(docker_repo) 90 | elif local_docker := os.environ.get("CONTAINER_LOCAL_PATH", None): 91 | return ecs.ContainerImage.from_asset(local_docker) 92 | else: 93 | return ecs.ContainerImage.from_asset("docker") 94 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9.0-slim-buster 2 | 3 | RUN apt-get update \ 4 | && apt-get install -y curl openssh-server \ 5 | && sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/g' /etc/ssh/sshd_config \ 6 | && mkdir -p /var/run/sshd 7 | EXPOSE 22 8 | COPY docker-entrypoint.sh /usr/local/bin/ 9 | RUN chmod +x /usr/local/bin/docker-entrypoint.sh 10 | ENTRYPOINT ["docker-entrypoint.sh"] 11 | -------------------------------------------------------------------------------- /docker/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -z "$SSH_PUBLIC_KEY" ]; then 4 | echo "Need your SSH public key as the SSH_PUBLIC_KEY env variable." 5 | exit 1 6 | fi 7 | 8 | # Create a folder to store user's SSH keys if it does not exist. 9 | USER_SSH_KEYS_FOLDER=~/.ssh 10 | [ ! -d "$USER_SSH_KEYS_FOLDER" ] && mkdir -p $USER_SSH_KEYS_FOLDER 11 | 12 | # Copy contents from the `SSH_PUBLIC_KEY` environment variable 13 | # to the `${USER_SSH_KEYS_FOLDER}/authorized_keys` file. 14 | # The environment variable must be set when the container starts. 15 | echo $SSH_PUBLIC_KEY > ${USER_SSH_KEYS_FOLDER}/authorized_keys 16 | 17 | # Clear the `SSH_PUBLIC_KEY` environment variable. 18 | unset SSH_PUBLIC_KEY 19 | 20 | # Start the SSH daemon. 21 | /usr/sbin/sshd -D -e 22 | -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | # Required Values: 2 | 3 | SSH_PRIVATE_KEY_LOCATION="/home/me/ssh_key" 4 | SSH_PUBLIC_KEY_LOCATION="/home/me/ssh_key.pub" 5 | 6 | # Optional Values, uncomment and populate when needed: 7 | 8 | # AWS_PROFILE="my-fave-aws-account" 9 | # IDENTIFIER="my-dev-1" 10 | # SSH_CONFIG_LOCATION="/Users/me/.ssh/config" 11 | # INSTANCE_CPU=2048 # 2 vCPU 12 | # INSTANCE_MEMORY=4096 # 4GB 13 | 14 | # # See Customising the container image for the heirarchy of these values: 15 | # CONTAINER_ECR_REPOSITORY="my-remote-workstation-repo-on-ecr" 16 | # CONTAINER_DOCKER_REPOSITORY="my-remote-workstation-repo-on-dockerhub" 17 | # CONTAINER_LOCAL_PATH="/Users/me/dev/remote-workstation/docker/custom/Dockerfile" 18 | 19 | # TAGS_MY_NAME="my-name" 20 | # TAGS_PROJECT="my-project-name" 21 | # TAGS_BILLING_CODE="123-billing" 22 | -------------------------------------------------------------------------------- /images/remote-ssh-hosts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/remote-workstation/897444dabda078ad7ec57f5d60cd40f41f80cc72/images/remote-ssh-hosts.png -------------------------------------------------------------------------------- /images/remote-ssh-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/remote-workstation/897444dabda078ad7ec57f5d60cd40f41f80cc72/images/remote-ssh-icon.png -------------------------------------------------------------------------------- /images/remote-ssh-index-html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/remote-workstation/897444dabda078ad7ec57f5d60cd40f41f80cc72/images/remote-ssh-index-html.png -------------------------------------------------------------------------------- /images/remote-ssh-jupyter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/remote-workstation/897444dabda078ad7ec57f5d60cd40f41f80cc72/images/remote-ssh-jupyter.png -------------------------------------------------------------------------------- /images/remote-ssh-port-forwarding-prompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/remote-workstation/897444dabda078ad7ec57f5d60cd40f41f80cc72/images/remote-ssh-port-forwarding-prompt.png -------------------------------------------------------------------------------- /images/remote-ssh-port-forwarding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/remote-workstation/897444dabda078ad7ec57f5d60cd40f41f80cc72/images/remote-ssh-port-forwarding.png -------------------------------------------------------------------------------- /images/remote-ssh-setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/remote-workstation/897444dabda078ad7ec57f5d60cd40f41f80cc72/images/remote-ssh-setup.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "aws-cdk": { 6 | "version": "1.95.1", 7 | "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-1.95.1.tgz", 8 | "integrity": "sha512-xQZQgBuK7TXRnlJo7TEWBDak42gtg1sCgJHYtcJ0VL+W9vlEBqaosEItcoSPPzXq4MzXZn39u45Rrv+gT6f5iw==", 9 | "requires": { 10 | "@aws-cdk/cloud-assembly-schema": "1.95.1", 11 | "@aws-cdk/cloudformation-diff": "1.95.1", 12 | "@aws-cdk/cx-api": "1.95.1", 13 | "@aws-cdk/region-info": "1.95.1", 14 | "@aws-cdk/yaml-cfn": "1.95.1", 15 | "archiver": "^5.3.0", 16 | "aws-sdk": "^2.848.0", 17 | "camelcase": "^6.2.0", 18 | "cdk-assets": "1.95.1", 19 | "colors": "^1.4.0", 20 | "decamelize": "^5.0.0", 21 | "fs-extra": "^9.1.0", 22 | "glob": "^7.1.6", 23 | "json-diff": "^0.5.4", 24 | "minimatch": ">=3.0", 25 | "promptly": "^3.2.0", 26 | "proxy-agent": "^4.0.1", 27 | "semver": "^7.3.5", 28 | "source-map-support": "^0.5.19", 29 | "table": "^6.0.7", 30 | "uuid": "^8.3.2", 31 | "wrap-ansi": "^7.0.0", 32 | "yargs": "^16.2.0" 33 | }, 34 | "dependencies": { 35 | "@aws-cdk/cfnspec": { 36 | "version": "1.95.1", 37 | "resolved": "https://registry.npmjs.org/@aws-cdk/cfnspec/-/cfnspec-1.95.1.tgz", 38 | "integrity": "sha512-x7t7BPBtgEZ5raiQhXZ7i1z2aGGcfoRLVmAnzkNNK+D9mz75ASjV2VD+13rYny5Lu+L8okDWyra4v5Hr4kiHEQ==", 39 | "requires": { 40 | "md5": "^2.3.0" 41 | } 42 | }, 43 | "@aws-cdk/cloud-assembly-schema": { 44 | "version": "1.95.1", 45 | "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-1.95.1.tgz", 46 | "integrity": "sha512-bCF/Jf45ookovaG1sYnGkqLJWprICbqWIdJyysOyzeRVNPKBfcs2FJUsQBqminXnQAYZnehQ1JKFx9k3a6lh/A==", 47 | "requires": { 48 | "jsonschema": "^1.4.0", 49 | "semver": "^7.3.5" 50 | } 51 | }, 52 | "@aws-cdk/cloudformation-diff": { 53 | "version": "1.95.1", 54 | "resolved": "https://registry.npmjs.org/@aws-cdk/cloudformation-diff/-/cloudformation-diff-1.95.1.tgz", 55 | "integrity": "sha512-WKvZnky2ZfmYM60JMrieIctCb5YG+hMjP6Tks5OiPtGz13m5A2zwUmB2LO+TNKJCi91LBO3VcEzVtVUbyaXZug==", 56 | "requires": { 57 | "@aws-cdk/cfnspec": "1.95.1", 58 | "colors": "^1.4.0", 59 | "diff": "^5.0.0", 60 | "fast-deep-equal": "^3.1.3", 61 | "string-width": "^4.2.2", 62 | "table": "^6.0.7" 63 | } 64 | }, 65 | "@aws-cdk/cx-api": { 66 | "version": "1.95.1", 67 | "resolved": "https://registry.npmjs.org/@aws-cdk/cx-api/-/cx-api-1.95.1.tgz", 68 | "integrity": "sha512-etfosIiLR3OnBYGHlnaBwmgiSHs9cCj+BpZOA/izrsezZ2helng0jul4AQyakgITZXV2aFv4LrRmUA7UdLO1TA==", 69 | "requires": { 70 | "@aws-cdk/cloud-assembly-schema": "1.95.1", 71 | "semver": "^7.3.5" 72 | } 73 | }, 74 | "@aws-cdk/region-info": { 75 | "version": "1.95.1", 76 | "resolved": "https://registry.npmjs.org/@aws-cdk/region-info/-/region-info-1.95.1.tgz", 77 | "integrity": "sha512-M9oPHdJs51uVBt15/jBvGW/oAHnHtxqmQwMr+E7cTZ/VByl91ASiGC5YmsY7gILlCTiFuThC4lGcZ3VRefxFUQ==" 78 | }, 79 | "@aws-cdk/yaml-cfn": { 80 | "version": "1.95.1", 81 | "resolved": "https://registry.npmjs.org/@aws-cdk/yaml-cfn/-/yaml-cfn-1.95.1.tgz", 82 | "integrity": "sha512-xa0PxQ7L97fUkS82BvmDhBqc5lWVBGpFFUfNNvbx6xs1tw+2c2tNn4yfA4zUs+L5SYDUD30QJqyALPcio2u7PQ==", 83 | "requires": { 84 | "yaml": "1.10.2" 85 | } 86 | }, 87 | "@tootallnate/once": { 88 | "version": "1.1.2", 89 | "resolved": "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82", 90 | "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" 91 | }, 92 | "agent-base": { 93 | "version": "6.0.2", 94 | "resolved": "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77", 95 | "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", 96 | "requires": { 97 | "debug": "4" 98 | } 99 | }, 100 | "ajv": { 101 | "version": "7.2.1", 102 | "resolved": "https://registry.yarnpkg.com/ajv/-/ajv-7.2.1.tgz#a5ac226171912447683524fa2f1248fcf8bac83d", 103 | "integrity": "sha512-+nu0HDv7kNSOua9apAVc979qd932rrZeb3WOvoiD31A/p1mIE5/9bN2027pE2rOPYEdS3UHzsvof4hY+lM9/WQ==", 104 | "requires": { 105 | "fast-deep-equal": "^3.1.1", 106 | "json-schema-traverse": "^1.0.0", 107 | "require-from-string": "^2.0.2", 108 | "uri-js": "^4.2.2" 109 | } 110 | }, 111 | "ansi-regex": { 112 | "version": "5.0.0", 113 | "resolved": "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75", 114 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" 115 | }, 116 | "ansi-styles": { 117 | "version": "4.3.0", 118 | "resolved": "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937", 119 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 120 | "requires": { 121 | "color-convert": "^2.0.1" 122 | } 123 | }, 124 | "archiver": { 125 | "version": "5.3.0", 126 | "resolved": "https://registry.yarnpkg.com/archiver/-/archiver-5.3.0.tgz#dd3e097624481741df626267564f7dd8640a45ba", 127 | "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", 128 | "requires": { 129 | "archiver-utils": "^2.1.0", 130 | "async": "^3.2.0", 131 | "buffer-crc32": "^0.2.1", 132 | "readable-stream": "^3.6.0", 133 | "readdir-glob": "^1.0.0", 134 | "tar-stream": "^2.2.0", 135 | "zip-stream": "^4.1.0" 136 | }, 137 | "dependencies": { 138 | "readable-stream": { 139 | "version": "3.6.0", 140 | "resolved": "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198", 141 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 142 | "requires": { 143 | "inherits": "^2.0.3", 144 | "string_decoder": "^1.1.1", 145 | "util-deprecate": "^1.0.1" 146 | } 147 | }, 148 | "safe-buffer": { 149 | "version": "5.2.1", 150 | "resolved": "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6", 151 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 152 | }, 153 | "string_decoder": { 154 | "version": "1.3.0", 155 | "resolved": "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e", 156 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 157 | "requires": { 158 | "safe-buffer": "~5.2.0" 159 | } 160 | } 161 | } 162 | }, 163 | "archiver-utils": { 164 | "version": "2.1.0", 165 | "resolved": "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2", 166 | "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", 167 | "requires": { 168 | "glob": "^7.1.4", 169 | "graceful-fs": "^4.2.0", 170 | "lazystream": "^1.0.0", 171 | "lodash.defaults": "^4.2.0", 172 | "lodash.difference": "^4.5.0", 173 | "lodash.flatten": "^4.4.0", 174 | "lodash.isplainobject": "^4.0.6", 175 | "lodash.union": "^4.6.0", 176 | "normalize-path": "^3.0.0", 177 | "readable-stream": "^2.0.0" 178 | } 179 | }, 180 | "ast-types": { 181 | "version": "0.13.4", 182 | "resolved": "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.4.tgz#ee0d77b343263965ecc3fb62da16e7222b2b6782", 183 | "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", 184 | "requires": { 185 | "tslib": "^2.0.1" 186 | } 187 | }, 188 | "astral-regex": { 189 | "version": "2.0.0", 190 | "resolved": "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31", 191 | "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==" 192 | }, 193 | "async": { 194 | "version": "3.2.0", 195 | "resolved": "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720", 196 | "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" 197 | }, 198 | "at-least-node": { 199 | "version": "1.0.0", 200 | "resolved": "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2", 201 | "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" 202 | }, 203 | "aws-sdk": { 204 | "version": "2.866.0", 205 | "resolved": "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.866.0.tgz#8150fb2e0cfecd281968edee7cad84598d8d7a09", 206 | "integrity": "sha512-6Z581Ek2Yfm78NpeEFMNuSoyiYG7tipEaqfWNFR1AGyYheZwql4ajhzzlpWn91LBpdm7qcFldSNY9U0tKpKWNw==", 207 | "requires": { 208 | "buffer": "4.9.2", 209 | "events": "1.1.1", 210 | "ieee754": "1.1.13", 211 | "jmespath": "0.15.0", 212 | "querystring": "0.2.0", 213 | "sax": "1.2.1", 214 | "url": "0.10.3", 215 | "uuid": "3.3.2", 216 | "xml2js": "0.4.19" 217 | }, 218 | "dependencies": { 219 | "buffer": { 220 | "version": "4.9.2", 221 | "resolved": "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8", 222 | "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", 223 | "requires": { 224 | "base64-js": "^1.0.2", 225 | "ieee754": "^1.1.4", 226 | "isarray": "^1.0.0" 227 | }, 228 | "dependencies": { 229 | "ieee754": { 230 | "version": "1.2.1", 231 | "resolved": "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352", 232 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" 233 | } 234 | } 235 | }, 236 | "ieee754": { 237 | "version": "1.1.13", 238 | "resolved": "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84", 239 | "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" 240 | }, 241 | "uuid": { 242 | "version": "3.3.2", 243 | "resolved": "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131", 244 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" 245 | } 246 | } 247 | }, 248 | "balanced-match": { 249 | "version": "1.0.0", 250 | "resolved": "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767", 251 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 252 | }, 253 | "base64-js": { 254 | "version": "1.5.1", 255 | "resolved": "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a", 256 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" 257 | }, 258 | "bl": { 259 | "version": "4.1.0", 260 | "resolved": "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a", 261 | "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", 262 | "requires": { 263 | "buffer": "^5.5.0", 264 | "inherits": "^2.0.4", 265 | "readable-stream": "^3.4.0" 266 | }, 267 | "dependencies": { 268 | "readable-stream": { 269 | "version": "3.6.0", 270 | "resolved": "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198", 271 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 272 | "requires": { 273 | "inherits": "^2.0.3", 274 | "string_decoder": "^1.1.1", 275 | "util-deprecate": "^1.0.1" 276 | } 277 | }, 278 | "safe-buffer": { 279 | "version": "5.2.1", 280 | "resolved": "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6", 281 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 282 | }, 283 | "string_decoder": { 284 | "version": "1.3.0", 285 | "resolved": "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e", 286 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 287 | "requires": { 288 | "safe-buffer": "~5.2.0" 289 | } 290 | } 291 | } 292 | }, 293 | "brace-expansion": { 294 | "version": "1.1.11", 295 | "resolved": "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd", 296 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 297 | "requires": { 298 | "balanced-match": "^1.0.0", 299 | "concat-map": "0.0.1" 300 | } 301 | }, 302 | "buffer": { 303 | "version": "5.7.1", 304 | "resolved": "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0", 305 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 306 | "requires": { 307 | "base64-js": "^1.3.1", 308 | "ieee754": "^1.1.13" 309 | } 310 | }, 311 | "buffer-crc32": { 312 | "version": "0.2.13", 313 | "resolved": "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242", 314 | "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" 315 | }, 316 | "buffer-from": { 317 | "version": "1.1.1", 318 | "resolved": "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef", 319 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" 320 | }, 321 | "bytes": { 322 | "version": "3.1.0", 323 | "resolved": "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6", 324 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 325 | }, 326 | "camelcase": { 327 | "version": "6.2.0", 328 | "resolved": "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809", 329 | "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==" 330 | }, 331 | "cdk-assets": { 332 | "version": "1.95.1", 333 | "resolved": "https://registry.npmjs.org/cdk-assets/-/cdk-assets-1.95.1.tgz", 334 | "integrity": "sha512-4QbBDBDbmQank/GVFyEx38f7VgCrGSnrtmGVBNJt3nXPvrVrgZLeUE5KGlW6zwnM0tdr6X+9tsu3nTHXT2Hpyg==", 335 | "requires": { 336 | "@aws-cdk/cloud-assembly-schema": "1.95.1", 337 | "@aws-cdk/cx-api": "1.95.1", 338 | "archiver": "^5.3.0", 339 | "aws-sdk": "^2.848.0", 340 | "glob": "^7.1.6", 341 | "yargs": "^16.2.0" 342 | } 343 | }, 344 | "charenc": { 345 | "version": "0.0.2", 346 | "resolved": "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667", 347 | "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" 348 | }, 349 | "cli-color": { 350 | "version": "0.1.7", 351 | "resolved": "https://registry.yarnpkg.com/cli-color/-/cli-color-0.1.7.tgz#adc3200fa471cc211b0da7f566b71e98b9d67347", 352 | "integrity": "sha1-rcMgD6RxzCEbDaf1ZrcemLnWc0c=", 353 | "requires": { 354 | "es5-ext": "0.8.x" 355 | } 356 | }, 357 | "cliui": { 358 | "version": "7.0.4", 359 | "resolved": "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f", 360 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", 361 | "requires": { 362 | "string-width": "^4.2.0", 363 | "strip-ansi": "^6.0.0", 364 | "wrap-ansi": "^7.0.0" 365 | } 366 | }, 367 | "color-convert": { 368 | "version": "2.0.1", 369 | "resolved": "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3", 370 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 371 | "requires": { 372 | "color-name": "~1.1.4" 373 | } 374 | }, 375 | "color-name": { 376 | "version": "1.1.4", 377 | "resolved": "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2", 378 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 379 | }, 380 | "colors": { 381 | "version": "1.4.0", 382 | "resolved": "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78", 383 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" 384 | }, 385 | "compress-commons": { 386 | "version": "4.1.0", 387 | "resolved": "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.0.tgz#25ec7a4528852ccd1d441a7d4353cd0ece11371b", 388 | "integrity": "sha512-ofaaLqfraD1YRTkrRKPCrGJ1pFeDG/MVCkVVV2FNGeWquSlqw5wOrwOfPQ1xF2u+blpeWASie5EubHz+vsNIgA==", 389 | "requires": { 390 | "buffer-crc32": "^0.2.13", 391 | "crc32-stream": "^4.0.1", 392 | "normalize-path": "^3.0.0", 393 | "readable-stream": "^3.6.0" 394 | }, 395 | "dependencies": { 396 | "readable-stream": { 397 | "version": "3.6.0", 398 | "resolved": "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198", 399 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 400 | "requires": { 401 | "inherits": "^2.0.3", 402 | "string_decoder": "^1.1.1", 403 | "util-deprecate": "^1.0.1" 404 | } 405 | }, 406 | "safe-buffer": { 407 | "version": "5.2.1", 408 | "resolved": "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6", 409 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 410 | }, 411 | "string_decoder": { 412 | "version": "1.3.0", 413 | "resolved": "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e", 414 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 415 | "requires": { 416 | "safe-buffer": "~5.2.0" 417 | } 418 | } 419 | } 420 | }, 421 | "concat-map": { 422 | "version": "0.0.1", 423 | "resolved": "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b", 424 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 425 | }, 426 | "core-util-is": { 427 | "version": "1.0.2", 428 | "resolved": "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7", 429 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 430 | }, 431 | "crc-32": { 432 | "version": "1.2.0", 433 | "resolved": "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208", 434 | "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", 435 | "requires": { 436 | "exit-on-epipe": "~1.0.1", 437 | "printj": "~1.1.0" 438 | } 439 | }, 440 | "crc32-stream": { 441 | "version": "4.0.2", 442 | "resolved": "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.2.tgz#c922ad22b38395abe9d3870f02fa8134ed709007", 443 | "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", 444 | "requires": { 445 | "crc-32": "^1.2.0", 446 | "readable-stream": "^3.4.0" 447 | }, 448 | "dependencies": { 449 | "readable-stream": { 450 | "version": "3.6.0", 451 | "resolved": "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198", 452 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 453 | "requires": { 454 | "inherits": "^2.0.3", 455 | "string_decoder": "^1.1.1", 456 | "util-deprecate": "^1.0.1" 457 | } 458 | }, 459 | "safe-buffer": { 460 | "version": "5.2.1", 461 | "resolved": "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6", 462 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 463 | }, 464 | "string_decoder": { 465 | "version": "1.3.0", 466 | "resolved": "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e", 467 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 468 | "requires": { 469 | "safe-buffer": "~5.2.0" 470 | } 471 | } 472 | } 473 | }, 474 | "crypt": { 475 | "version": "0.0.2", 476 | "resolved": "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b", 477 | "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" 478 | }, 479 | "data-uri-to-buffer": { 480 | "version": "3.0.1", 481 | "resolved": "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636", 482 | "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==" 483 | }, 484 | "debug": { 485 | "version": "4.3.1", 486 | "resolved": "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee", 487 | "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", 488 | "requires": { 489 | "ms": "2.1.2" 490 | } 491 | }, 492 | "decamelize": { 493 | "version": "5.0.0", 494 | "resolved": "https://registry.yarnpkg.com/decamelize/-/decamelize-5.0.0.tgz#88358157b010ef133febfd27c18994bd80c6215b", 495 | "integrity": "sha512-U75DcT5hrio3KNtvdULAWnLiAPbFUC4191ldxMmj4FA/mRuBnmDwU0boNfPyFRhnan+Jm+haLeSn3P0afcBn4w==" 496 | }, 497 | "deep-is": { 498 | "version": "0.1.3", 499 | "resolved": "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34", 500 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" 501 | }, 502 | "degenerator": { 503 | "version": "2.2.0", 504 | "resolved": "https://registry.yarnpkg.com/degenerator/-/degenerator-2.2.0.tgz#49e98c11fa0293c5b26edfbb52f15729afcdb254", 505 | "integrity": "sha512-aiQcQowF01RxFI4ZLFMpzyotbQonhNpBao6dkI8JPk5a+hmSjR5ErHp2CQySmQe8os3VBqLCIh87nDBgZXvsmg==", 506 | "requires": { 507 | "ast-types": "^0.13.2", 508 | "escodegen": "^1.8.1", 509 | "esprima": "^4.0.0" 510 | } 511 | }, 512 | "depd": { 513 | "version": "1.1.2", 514 | "resolved": "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9", 515 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 516 | }, 517 | "diff": { 518 | "version": "5.0.0", 519 | "resolved": "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b", 520 | "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==" 521 | }, 522 | "difflib": { 523 | "version": "0.2.4", 524 | "resolved": "https://registry.yarnpkg.com/difflib/-/difflib-0.2.4.tgz#b5e30361a6db023176d562892db85940a718f47e", 525 | "integrity": "sha1-teMDYabbAjF21WKJLbhZQKcY9H4=", 526 | "requires": { 527 | "heap": ">= 0.2.0" 528 | } 529 | }, 530 | "dreamopt": { 531 | "version": "0.6.0", 532 | "resolved": "https://registry.yarnpkg.com/dreamopt/-/dreamopt-0.6.0.tgz#d813ccdac8d39d8ad526775514a13dda664d6b4b", 533 | "integrity": "sha1-2BPM2sjTnYrVJndVFKE92mZNa0s=", 534 | "requires": { 535 | "wordwrap": ">=0.0.2" 536 | } 537 | }, 538 | "emoji-regex": { 539 | "version": "8.0.0", 540 | "resolved": "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37", 541 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 542 | }, 543 | "end-of-stream": { 544 | "version": "1.4.4", 545 | "resolved": "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0", 546 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 547 | "requires": { 548 | "once": "^1.4.0" 549 | } 550 | }, 551 | "es5-ext": { 552 | "version": "0.8.2", 553 | "resolved": "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.8.2.tgz#aba8d9e1943a895ac96837a62a39b3f55ecd94ab", 554 | "integrity": "sha1-q6jZ4ZQ6iVrJaDemKjmz9V7NlKs=" 555 | }, 556 | "escalade": { 557 | "version": "3.1.1", 558 | "resolved": "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40", 559 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" 560 | }, 561 | "escodegen": { 562 | "version": "1.14.3", 563 | "resolved": "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503", 564 | "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", 565 | "requires": { 566 | "esprima": "^4.0.1", 567 | "estraverse": "^4.2.0", 568 | "esutils": "^2.0.2", 569 | "optionator": "^0.8.1", 570 | "source-map": "~0.6.1" 571 | } 572 | }, 573 | "esprima": { 574 | "version": "4.0.1", 575 | "resolved": "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71", 576 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" 577 | }, 578 | "estraverse": { 579 | "version": "4.3.0", 580 | "resolved": "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d", 581 | "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" 582 | }, 583 | "esutils": { 584 | "version": "2.0.3", 585 | "resolved": "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64", 586 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" 587 | }, 588 | "events": { 589 | "version": "1.1.1", 590 | "resolved": "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924", 591 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" 592 | }, 593 | "exit-on-epipe": { 594 | "version": "1.0.1", 595 | "resolved": "https://registry.yarnpkg.com/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692", 596 | "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==" 597 | }, 598 | "fast-deep-equal": { 599 | "version": "3.1.3", 600 | "resolved": "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525", 601 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" 602 | }, 603 | "fast-levenshtein": { 604 | "version": "2.0.6", 605 | "resolved": "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917", 606 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" 607 | }, 608 | "file-uri-to-path": { 609 | "version": "2.0.0", 610 | "resolved": "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz#7b415aeba227d575851e0a5b0c640d7656403fba", 611 | "integrity": "sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg==" 612 | }, 613 | "fs-constants": { 614 | "version": "1.0.0", 615 | "resolved": "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad", 616 | "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" 617 | }, 618 | "fs-extra": { 619 | "version": "9.1.0", 620 | "resolved": "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d", 621 | "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", 622 | "requires": { 623 | "at-least-node": "^1.0.0", 624 | "graceful-fs": "^4.2.0", 625 | "jsonfile": "^6.0.1", 626 | "universalify": "^2.0.0" 627 | } 628 | }, 629 | "fs.realpath": { 630 | "version": "1.0.0", 631 | "resolved": "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f", 632 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 633 | }, 634 | "ftp": { 635 | "version": "0.3.10", 636 | "resolved": "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d", 637 | "integrity": "sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=", 638 | "requires": { 639 | "readable-stream": "1.1.x", 640 | "xregexp": "2.0.0" 641 | }, 642 | "dependencies": { 643 | "isarray": { 644 | "version": "0.0.1", 645 | "resolved": "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf", 646 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" 647 | }, 648 | "readable-stream": { 649 | "version": "1.1.14", 650 | "resolved": "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9", 651 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", 652 | "requires": { 653 | "core-util-is": "~1.0.0", 654 | "inherits": "~2.0.1", 655 | "isarray": "0.0.1", 656 | "string_decoder": "~0.10.x" 657 | } 658 | }, 659 | "string_decoder": { 660 | "version": "0.10.31", 661 | "resolved": "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94", 662 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" 663 | } 664 | } 665 | }, 666 | "get-caller-file": { 667 | "version": "2.0.5", 668 | "resolved": "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e", 669 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" 670 | }, 671 | "get-uri": { 672 | "version": "3.0.2", 673 | "resolved": "https://registry.yarnpkg.com/get-uri/-/get-uri-3.0.2.tgz#f0ef1356faabc70e1f9404fa3b66b2ba9bfc725c", 674 | "integrity": "sha512-+5s0SJbGoyiJTZZ2JTpFPLMPSch72KEqGOTvQsBqg0RBWvwhWUSYZFAtz3TPW0GXJuLBJPts1E241iHg+VRfhg==", 675 | "requires": { 676 | "@tootallnate/once": "1", 677 | "data-uri-to-buffer": "3", 678 | "debug": "4", 679 | "file-uri-to-path": "2", 680 | "fs-extra": "^8.1.0", 681 | "ftp": "^0.3.10" 682 | }, 683 | "dependencies": { 684 | "fs-extra": { 685 | "version": "8.1.0", 686 | "resolved": "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0", 687 | "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", 688 | "requires": { 689 | "graceful-fs": "^4.2.0", 690 | "jsonfile": "^4.0.0", 691 | "universalify": "^0.1.0" 692 | } 693 | }, 694 | "jsonfile": { 695 | "version": "4.0.0", 696 | "resolved": "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb", 697 | "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", 698 | "requires": { 699 | "graceful-fs": "^4.1.6" 700 | } 701 | }, 702 | "universalify": { 703 | "version": "0.1.2", 704 | "resolved": "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66", 705 | "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" 706 | } 707 | } 708 | }, 709 | "glob": { 710 | "version": "7.1.6", 711 | "resolved": "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6", 712 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 713 | "requires": { 714 | "fs.realpath": "^1.0.0", 715 | "inflight": "^1.0.4", 716 | "inherits": "2", 717 | "minimatch": "^3.0.4", 718 | "once": "^1.3.0", 719 | "path-is-absolute": "^1.0.0" 720 | } 721 | }, 722 | "graceful-fs": { 723 | "version": "4.2.6", 724 | "resolved": "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee", 725 | "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" 726 | }, 727 | "heap": { 728 | "version": "0.2.6", 729 | "resolved": "https://registry.yarnpkg.com/heap/-/heap-0.2.6.tgz#087e1f10b046932fc8594dd9e6d378afc9d1e5ac", 730 | "integrity": "sha1-CH4fELBGky/IWU3Z5tN4r8nR5aw=" 731 | }, 732 | "http-errors": { 733 | "version": "1.7.3", 734 | "resolved": "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06", 735 | "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", 736 | "requires": { 737 | "depd": "~1.1.2", 738 | "inherits": "2.0.4", 739 | "setprototypeof": "1.1.1", 740 | "statuses": ">= 1.5.0 < 2", 741 | "toidentifier": "1.0.0" 742 | } 743 | }, 744 | "http-proxy-agent": { 745 | "version": "4.0.1", 746 | "resolved": "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a", 747 | "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", 748 | "requires": { 749 | "@tootallnate/once": "1", 750 | "agent-base": "6", 751 | "debug": "4" 752 | } 753 | }, 754 | "https-proxy-agent": { 755 | "version": "5.0.0", 756 | "resolved": "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2", 757 | "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", 758 | "requires": { 759 | "agent-base": "6", 760 | "debug": "4" 761 | } 762 | }, 763 | "iconv-lite": { 764 | "version": "0.4.24", 765 | "resolved": "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b", 766 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 767 | "requires": { 768 | "safer-buffer": ">= 2.1.2 < 3" 769 | } 770 | }, 771 | "ieee754": { 772 | "version": "1.2.1", 773 | "resolved": "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352", 774 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" 775 | }, 776 | "inflight": { 777 | "version": "1.0.6", 778 | "resolved": "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9", 779 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 780 | "requires": { 781 | "once": "^1.3.0", 782 | "wrappy": "1" 783 | } 784 | }, 785 | "inherits": { 786 | "version": "2.0.4", 787 | "resolved": "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c", 788 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 789 | }, 790 | "ip": { 791 | "version": "1.1.5", 792 | "resolved": "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a", 793 | "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" 794 | }, 795 | "is-buffer": { 796 | "version": "1.1.6", 797 | "resolved": "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be", 798 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 799 | }, 800 | "is-fullwidth-code-point": { 801 | "version": "3.0.0", 802 | "resolved": "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d", 803 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" 804 | }, 805 | "isarray": { 806 | "version": "1.0.0", 807 | "resolved": "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11", 808 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 809 | }, 810 | "jmespath": { 811 | "version": "0.15.0", 812 | "resolved": "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217", 813 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" 814 | }, 815 | "json-diff": { 816 | "version": "0.5.4", 817 | "resolved": "https://registry.yarnpkg.com/json-diff/-/json-diff-0.5.4.tgz#7bc8198c441756632aab66c7d9189d365a7a035a", 818 | "integrity": "sha512-q5Xmx9QXNOzOzIlMoYtLrLiu4Jl/Ce2bn0CNcv54PhyH89CI4GWlGVDye8ei2Ijt9R3U+vsWPsXpLUNob8bs8Q==", 819 | "requires": { 820 | "cli-color": "~0.1.6", 821 | "difflib": "~0.2.1", 822 | "dreamopt": "~0.6.0" 823 | } 824 | }, 825 | "json-schema-traverse": { 826 | "version": "1.0.0", 827 | "resolved": "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2", 828 | "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" 829 | }, 830 | "jsonfile": { 831 | "version": "6.1.0", 832 | "resolved": "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae", 833 | "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", 834 | "requires": { 835 | "graceful-fs": "^4.1.6", 836 | "universalify": "^2.0.0" 837 | } 838 | }, 839 | "jsonschema": { 840 | "version": "1.4.0", 841 | "resolved": "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.0.tgz#1afa34c4bc22190d8e42271ec17ac8b3404f87b2", 842 | "integrity": "sha512-/YgW6pRMr6M7C+4o8kS+B/2myEpHCrxO4PEWnqJNBFMjn7EWXqlQ4tGwL6xTHeRplwuZmcAncdvfOad1nT2yMw==" 843 | }, 844 | "lazystream": { 845 | "version": "1.0.0", 846 | "resolved": "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4", 847 | "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", 848 | "requires": { 849 | "readable-stream": "^2.0.5" 850 | } 851 | }, 852 | "levn": { 853 | "version": "0.3.0", 854 | "resolved": "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee", 855 | "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", 856 | "requires": { 857 | "prelude-ls": "~1.1.2", 858 | "type-check": "~0.3.2" 859 | } 860 | }, 861 | "lodash": { 862 | "version": "4.17.21", 863 | "resolved": "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c", 864 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 865 | }, 866 | "lodash.defaults": { 867 | "version": "4.2.0", 868 | "resolved": "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c", 869 | "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" 870 | }, 871 | "lodash.difference": { 872 | "version": "4.5.0", 873 | "resolved": "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c", 874 | "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" 875 | }, 876 | "lodash.flatten": { 877 | "version": "4.4.0", 878 | "resolved": "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f", 879 | "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" 880 | }, 881 | "lodash.isplainobject": { 882 | "version": "4.0.6", 883 | "resolved": "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb", 884 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" 885 | }, 886 | "lodash.union": { 887 | "version": "4.6.0", 888 | "resolved": "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88", 889 | "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" 890 | }, 891 | "lru-cache": { 892 | "version": "5.1.1", 893 | "resolved": "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920", 894 | "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", 895 | "requires": { 896 | "yallist": "^3.0.2" 897 | } 898 | }, 899 | "md5": { 900 | "version": "2.3.0", 901 | "resolved": "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f", 902 | "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", 903 | "requires": { 904 | "charenc": "0.0.2", 905 | "crypt": "0.0.2", 906 | "is-buffer": "~1.1.6" 907 | } 908 | }, 909 | "minimatch": { 910 | "version": "3.0.4", 911 | "resolved": "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083", 912 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 913 | "requires": { 914 | "brace-expansion": "^1.1.7" 915 | } 916 | }, 917 | "ms": { 918 | "version": "2.1.2", 919 | "resolved": "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009", 920 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 921 | }, 922 | "mute-stream": { 923 | "version": "0.0.8", 924 | "resolved": "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d", 925 | "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" 926 | }, 927 | "netmask": { 928 | "version": "1.0.6", 929 | "resolved": "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35", 930 | "integrity": "sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=" 931 | }, 932 | "normalize-path": { 933 | "version": "3.0.0", 934 | "resolved": "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65", 935 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" 936 | }, 937 | "once": { 938 | "version": "1.4.0", 939 | "resolved": "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1", 940 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 941 | "requires": { 942 | "wrappy": "1" 943 | } 944 | }, 945 | "optionator": { 946 | "version": "0.8.3", 947 | "resolved": "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495", 948 | "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", 949 | "requires": { 950 | "deep-is": "~0.1.3", 951 | "fast-levenshtein": "~2.0.6", 952 | "levn": "~0.3.0", 953 | "prelude-ls": "~1.1.2", 954 | "type-check": "~0.3.2", 955 | "word-wrap": "~1.2.3" 956 | } 957 | }, 958 | "pac-proxy-agent": { 959 | "version": "4.1.0", 960 | "resolved": "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-4.1.0.tgz#66883eeabadc915fc5e95457324cb0f0ac78defb", 961 | "integrity": "sha512-ejNgYm2HTXSIYX9eFlkvqFp8hyJ374uDf0Zq5YUAifiSh1D6fo+iBivQZirGvVv8dCYUsLhmLBRhlAYvBKI5+Q==", 962 | "requires": { 963 | "@tootallnate/once": "1", 964 | "agent-base": "6", 965 | "debug": "4", 966 | "get-uri": "3", 967 | "http-proxy-agent": "^4.0.1", 968 | "https-proxy-agent": "5", 969 | "pac-resolver": "^4.1.0", 970 | "raw-body": "^2.2.0", 971 | "socks-proxy-agent": "5" 972 | } 973 | }, 974 | "pac-resolver": { 975 | "version": "4.1.0", 976 | "resolved": "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-4.1.0.tgz#4b12e7d096b255a3b84e53f6831f32e9c7e5fe95", 977 | "integrity": "sha512-d6lf2IrZJJ7ooVHr7BfwSjRO1yKSJMaiiWYSHcrxSIUtZrCa4KKGwcztdkZ/E9LFleJfjoi1yl+XLR7AX24nbQ==", 978 | "requires": { 979 | "degenerator": "^2.2.0", 980 | "ip": "^1.1.5", 981 | "netmask": "^1.0.6" 982 | } 983 | }, 984 | "path-is-absolute": { 985 | "version": "1.0.1", 986 | "resolved": "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f", 987 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 988 | }, 989 | "prelude-ls": { 990 | "version": "1.1.2", 991 | "resolved": "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54", 992 | "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" 993 | }, 994 | "printj": { 995 | "version": "1.1.2", 996 | "resolved": "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222", 997 | "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==" 998 | }, 999 | "process-nextick-args": { 1000 | "version": "2.0.1", 1001 | "resolved": "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2", 1002 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 1003 | }, 1004 | "promptly": { 1005 | "version": "3.2.0", 1006 | "resolved": "https://registry.yarnpkg.com/promptly/-/promptly-3.2.0.tgz#a5517fbbf59bd31c1751d4e1d9bef1714f42b9d8", 1007 | "integrity": "sha512-WnR9obtgW+rG4oUV3hSnNGl1pHm3V1H/qD9iJBumGSmVsSC5HpZOLuu8qdMb6yCItGfT7dcRszejr/5P3i9Pug==", 1008 | "requires": { 1009 | "read": "^1.0.4" 1010 | } 1011 | }, 1012 | "proxy-agent": { 1013 | "version": "4.0.1", 1014 | "resolved": "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-4.0.1.tgz#326c3250776c7044cd19655ccbfadf2e065a045c", 1015 | "integrity": "sha512-ODnQnW2jc/FUVwHHuaZEfN5otg/fMbvMxz9nMSUQfJ9JU7q2SZvSULSsjLloVgJOiv9yhc8GlNMKc4GkFmcVEA==", 1016 | "requires": { 1017 | "agent-base": "^6.0.0", 1018 | "debug": "4", 1019 | "http-proxy-agent": "^4.0.0", 1020 | "https-proxy-agent": "^5.0.0", 1021 | "lru-cache": "^5.1.1", 1022 | "pac-proxy-agent": "^4.1.0", 1023 | "proxy-from-env": "^1.0.0", 1024 | "socks-proxy-agent": "^5.0.0" 1025 | } 1026 | }, 1027 | "proxy-from-env": { 1028 | "version": "1.1.0", 1029 | "resolved": "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2", 1030 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 1031 | }, 1032 | "punycode": { 1033 | "version": "1.3.2", 1034 | "resolved": "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d", 1035 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" 1036 | }, 1037 | "querystring": { 1038 | "version": "0.2.0", 1039 | "resolved": "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620", 1040 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" 1041 | }, 1042 | "raw-body": { 1043 | "version": "2.4.1", 1044 | "resolved": "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c", 1045 | "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", 1046 | "requires": { 1047 | "bytes": "3.1.0", 1048 | "http-errors": "1.7.3", 1049 | "iconv-lite": "0.4.24", 1050 | "unpipe": "1.0.0" 1051 | } 1052 | }, 1053 | "read": { 1054 | "version": "1.0.7", 1055 | "resolved": "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4", 1056 | "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", 1057 | "requires": { 1058 | "mute-stream": "~0.0.4" 1059 | } 1060 | }, 1061 | "readable-stream": { 1062 | "version": "2.3.7", 1063 | "resolved": "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57", 1064 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 1065 | "requires": { 1066 | "core-util-is": "~1.0.0", 1067 | "inherits": "~2.0.3", 1068 | "isarray": "~1.0.0", 1069 | "process-nextick-args": "~2.0.0", 1070 | "safe-buffer": "~5.1.1", 1071 | "string_decoder": "~1.1.1", 1072 | "util-deprecate": "~1.0.1" 1073 | } 1074 | }, 1075 | "readdir-glob": { 1076 | "version": "1.1.1", 1077 | "resolved": "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.1.tgz#f0e10bb7bf7bfa7e0add8baffdc54c3f7dbee6c4", 1078 | "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", 1079 | "requires": { 1080 | "minimatch": "^3.0.4" 1081 | } 1082 | }, 1083 | "require-directory": { 1084 | "version": "2.1.1", 1085 | "resolved": "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42", 1086 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" 1087 | }, 1088 | "require-from-string": { 1089 | "version": "2.0.2", 1090 | "resolved": "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909", 1091 | "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" 1092 | }, 1093 | "safe-buffer": { 1094 | "version": "5.1.2", 1095 | "resolved": "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d", 1096 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 1097 | }, 1098 | "safer-buffer": { 1099 | "version": "2.1.2", 1100 | "resolved": "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a", 1101 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1102 | }, 1103 | "sax": { 1104 | "version": "1.2.1", 1105 | "resolved": "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a", 1106 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" 1107 | }, 1108 | "semver": { 1109 | "version": "7.3.5", 1110 | "resolved": "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7", 1111 | "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", 1112 | "requires": { 1113 | "lru-cache": "^6.0.0" 1114 | }, 1115 | "dependencies": { 1116 | "lru-cache": { 1117 | "version": "6.0.0", 1118 | "resolved": "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94", 1119 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 1120 | "requires": { 1121 | "yallist": "^4.0.0" 1122 | } 1123 | }, 1124 | "yallist": { 1125 | "version": "4.0.0", 1126 | "resolved": "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72", 1127 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 1128 | } 1129 | } 1130 | }, 1131 | "setprototypeof": { 1132 | "version": "1.1.1", 1133 | "resolved": "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683", 1134 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 1135 | }, 1136 | "slice-ansi": { 1137 | "version": "4.0.0", 1138 | "resolved": "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b", 1139 | "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", 1140 | "requires": { 1141 | "ansi-styles": "^4.0.0", 1142 | "astral-regex": "^2.0.0", 1143 | "is-fullwidth-code-point": "^3.0.0" 1144 | } 1145 | }, 1146 | "smart-buffer": { 1147 | "version": "4.1.0", 1148 | "resolved": "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba", 1149 | "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==" 1150 | }, 1151 | "socks": { 1152 | "version": "2.6.0", 1153 | "resolved": "https://registry.yarnpkg.com/socks/-/socks-2.6.0.tgz#6b984928461d39871b3666754b9000ecf39dfac2", 1154 | "integrity": "sha512-mNmr9owlinMplev0Wd7UHFlqI4ofnBnNzFuzrm63PPaHgbkqCFe4T5LzwKmtQ/f2tX0NTpcdVLyD/FHxFBstYw==", 1155 | "requires": { 1156 | "ip": "^1.1.5", 1157 | "smart-buffer": "^4.1.0" 1158 | } 1159 | }, 1160 | "socks-proxy-agent": { 1161 | "version": "5.0.0", 1162 | "resolved": "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz#7c0f364e7b1cf4a7a437e71253bed72e9004be60", 1163 | "integrity": "sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA==", 1164 | "requires": { 1165 | "agent-base": "6", 1166 | "debug": "4", 1167 | "socks": "^2.3.3" 1168 | } 1169 | }, 1170 | "source-map": { 1171 | "version": "0.6.1", 1172 | "resolved": "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263", 1173 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 1174 | }, 1175 | "source-map-support": { 1176 | "version": "0.5.19", 1177 | "resolved": "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61", 1178 | "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", 1179 | "requires": { 1180 | "buffer-from": "^1.0.0", 1181 | "source-map": "^0.6.0" 1182 | } 1183 | }, 1184 | "statuses": { 1185 | "version": "1.5.0", 1186 | "resolved": "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c", 1187 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 1188 | }, 1189 | "string-width": { 1190 | "version": "4.2.2", 1191 | "resolved": "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5", 1192 | "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", 1193 | "requires": { 1194 | "emoji-regex": "^8.0.0", 1195 | "is-fullwidth-code-point": "^3.0.0", 1196 | "strip-ansi": "^6.0.0" 1197 | } 1198 | }, 1199 | "string_decoder": { 1200 | "version": "1.1.1", 1201 | "resolved": "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8", 1202 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1203 | "requires": { 1204 | "safe-buffer": "~5.1.0" 1205 | } 1206 | }, 1207 | "strip-ansi": { 1208 | "version": "6.0.0", 1209 | "resolved": "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532", 1210 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", 1211 | "requires": { 1212 | "ansi-regex": "^5.0.0" 1213 | } 1214 | }, 1215 | "table": { 1216 | "version": "6.0.7", 1217 | "resolved": "https://registry.yarnpkg.com/table/-/table-6.0.7.tgz#e45897ffbcc1bcf9e8a87bf420f2c9e5a7a52a34", 1218 | "integrity": "sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==", 1219 | "requires": { 1220 | "ajv": "^7.0.2", 1221 | "lodash": "^4.17.20", 1222 | "slice-ansi": "^4.0.0", 1223 | "string-width": "^4.2.0" 1224 | } 1225 | }, 1226 | "tar-stream": { 1227 | "version": "2.2.0", 1228 | "resolved": "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287", 1229 | "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", 1230 | "requires": { 1231 | "bl": "^4.0.3", 1232 | "end-of-stream": "^1.4.1", 1233 | "fs-constants": "^1.0.0", 1234 | "inherits": "^2.0.3", 1235 | "readable-stream": "^3.1.1" 1236 | }, 1237 | "dependencies": { 1238 | "readable-stream": { 1239 | "version": "3.6.0", 1240 | "resolved": "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198", 1241 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 1242 | "requires": { 1243 | "inherits": "^2.0.3", 1244 | "string_decoder": "^1.1.1", 1245 | "util-deprecate": "^1.0.1" 1246 | } 1247 | }, 1248 | "safe-buffer": { 1249 | "version": "5.2.1", 1250 | "resolved": "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6", 1251 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 1252 | }, 1253 | "string_decoder": { 1254 | "version": "1.3.0", 1255 | "resolved": "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e", 1256 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 1257 | "requires": { 1258 | "safe-buffer": "~5.2.0" 1259 | } 1260 | } 1261 | } 1262 | }, 1263 | "toidentifier": { 1264 | "version": "1.0.0", 1265 | "resolved": "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553", 1266 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 1267 | }, 1268 | "tslib": { 1269 | "version": "2.1.0", 1270 | "resolved": "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a", 1271 | "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" 1272 | }, 1273 | "type-check": { 1274 | "version": "0.3.2", 1275 | "resolved": "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72", 1276 | "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", 1277 | "requires": { 1278 | "prelude-ls": "~1.1.2" 1279 | } 1280 | }, 1281 | "universalify": { 1282 | "version": "2.0.0", 1283 | "resolved": "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717", 1284 | "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" 1285 | }, 1286 | "unpipe": { 1287 | "version": "1.0.0", 1288 | "resolved": "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec", 1289 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 1290 | }, 1291 | "uri-js": { 1292 | "version": "4.4.1", 1293 | "resolved": "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e", 1294 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 1295 | "requires": { 1296 | "punycode": "^2.1.0" 1297 | }, 1298 | "dependencies": { 1299 | "punycode": { 1300 | "version": "2.1.1", 1301 | "resolved": "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec", 1302 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 1303 | } 1304 | } 1305 | }, 1306 | "url": { 1307 | "version": "0.10.3", 1308 | "resolved": "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64", 1309 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 1310 | "requires": { 1311 | "punycode": "1.3.2", 1312 | "querystring": "0.2.0" 1313 | } 1314 | }, 1315 | "util-deprecate": { 1316 | "version": "1.0.2", 1317 | "resolved": "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf", 1318 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1319 | }, 1320 | "uuid": { 1321 | "version": "8.3.2", 1322 | "resolved": "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2", 1323 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" 1324 | }, 1325 | "word-wrap": { 1326 | "version": "1.2.3", 1327 | "resolved": "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c", 1328 | "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" 1329 | }, 1330 | "wordwrap": { 1331 | "version": "1.0.0", 1332 | "resolved": "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb", 1333 | "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" 1334 | }, 1335 | "wrap-ansi": { 1336 | "version": "7.0.0", 1337 | "resolved": "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43", 1338 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 1339 | "requires": { 1340 | "ansi-styles": "^4.0.0", 1341 | "string-width": "^4.1.0", 1342 | "strip-ansi": "^6.0.0" 1343 | } 1344 | }, 1345 | "wrappy": { 1346 | "version": "1.0.2", 1347 | "resolved": "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f", 1348 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 1349 | }, 1350 | "xml2js": { 1351 | "version": "0.4.19", 1352 | "resolved": "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7", 1353 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 1354 | "requires": { 1355 | "sax": ">=0.6.0", 1356 | "xmlbuilder": "~9.0.1" 1357 | }, 1358 | "dependencies": { 1359 | "sax": { 1360 | "version": "1.2.4", 1361 | "resolved": "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9", 1362 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" 1363 | } 1364 | } 1365 | }, 1366 | "xmlbuilder": { 1367 | "version": "9.0.7", 1368 | "resolved": "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d", 1369 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" 1370 | }, 1371 | "xregexp": { 1372 | "version": "2.0.0", 1373 | "resolved": "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943", 1374 | "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=" 1375 | }, 1376 | "y18n": { 1377 | "version": "5.0.5", 1378 | "resolved": "https://registry.yarnpkg.com/y18n/-/y18n-5.0.5.tgz#8769ec08d03b1ea2df2500acef561743bbb9ab18", 1379 | "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==" 1380 | }, 1381 | "yallist": { 1382 | "version": "3.1.1", 1383 | "resolved": "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd", 1384 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" 1385 | }, 1386 | "yaml": { 1387 | "version": "1.10.2", 1388 | "resolved": "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b", 1389 | "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" 1390 | }, 1391 | "yargs": { 1392 | "version": "16.2.0", 1393 | "resolved": "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66", 1394 | "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", 1395 | "requires": { 1396 | "cliui": "^7.0.2", 1397 | "escalade": "^3.1.1", 1398 | "get-caller-file": "^2.0.5", 1399 | "require-directory": "^2.1.1", 1400 | "string-width": "^4.2.0", 1401 | "y18n": "^5.0.5", 1402 | "yargs-parser": "^20.2.2" 1403 | } 1404 | }, 1405 | "yargs-parser": { 1406 | "version": "20.2.7", 1407 | "resolved": "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a", 1408 | "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==" 1409 | }, 1410 | "zip-stream": { 1411 | "version": "4.1.0", 1412 | "resolved": "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.0.tgz#51dd326571544e36aa3f756430b313576dc8fc79", 1413 | "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", 1414 | "requires": { 1415 | "archiver-utils": "^2.1.0", 1416 | "compress-commons": "^4.1.0", 1417 | "readable-stream": "^3.6.0" 1418 | }, 1419 | "dependencies": { 1420 | "readable-stream": { 1421 | "version": "3.6.0", 1422 | "resolved": "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198", 1423 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 1424 | "requires": { 1425 | "inherits": "^2.0.3", 1426 | "string_decoder": "^1.1.1", 1427 | "util-deprecate": "^1.0.1" 1428 | } 1429 | }, 1430 | "safe-buffer": { 1431 | "version": "5.2.1", 1432 | "resolved": "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6", 1433 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 1434 | }, 1435 | "string_decoder": { 1436 | "version": "1.3.0", 1437 | "resolved": "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e", 1438 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 1439 | "requires": { 1440 | "safe-buffer": "~5.2.0" 1441 | } 1442 | } 1443 | } 1444 | } 1445 | } 1446 | } 1447 | } 1448 | } 1449 | -------------------------------------------------------------------------------- /utils/generate_ssh_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | from pathlib import Path 4 | 5 | import boto3 6 | 7 | IDENTIFIER = os.environ.get("IDENTIFIER", "dev") 8 | SSH_CONFIG_ENTRY_REGEX = ( 9 | r"# Generated by the remote-workstation project\n" 10 | rf"Host remote-workstation-{IDENTIFIER}\n.*\n.*\n.*" 11 | ) 12 | 13 | 14 | def get_instance_ip() -> str: 15 | """ 16 | For a given identifier for a deployment (env var of IDENTIFIER), find the cluster 17 | that was deployed, find the tasks within the cluster (there should only be one), 18 | find the network interfaces on that task, and return the public IP of the instance 19 | :returns: str The public ip of the remote instance 20 | """ 21 | ecs_c = boto3.client("ecs") 22 | task_arns = ecs_c.list_tasks( 23 | cluster=f"remote-cluster-{IDENTIFIER}", desiredStatus="RUNNING" 24 | )["taskArns"] 25 | if task_arns: 26 | tasks = ecs_c.describe_tasks( 27 | cluster=f"remote-cluster-{IDENTIFIER}", tasks=task_arns 28 | )["tasks"] 29 | # Should only ever be one task and network interface on deployment 30 | task_details = { 31 | d["name"]: d["value"] for d in tasks[0]["attachments"][0]["details"] 32 | } 33 | interface_id = task_details["networkInterfaceId"] 34 | ec2_c = boto3.client("ec2") 35 | network_interfaces = ec2_c.describe_network_interfaces( 36 | NetworkInterfaceIds=[interface_id] 37 | )["NetworkInterfaces"] 38 | return network_interfaces[0]["Association"]["PublicIp"] 39 | else: 40 | return None 41 | 42 | 43 | def generate_ssh_config_entry(instance_ip: str) -> str: 44 | """ 45 | Given an instance IP address, generate a SSH config entry 46 | :param instance_ip: str representing the IP address of the instance 47 | :returns: str The generated SSH config entry 48 | """ 49 | return f"""# Generated by the remote-workstation project 50 | Host remote-workstation-{IDENTIFIER} 51 | HostName {instance_ip} 52 | User root 53 | IdentityFile {os.environ["SSH_PRIVATE_KEY_LOCATION"]}""" 54 | 55 | 56 | def find_previous_config_entry_and_replace_or_write_entry_if_not_present( 57 | ssh_config_entry: str, config_location: str, config_location_dir: str 58 | ): 59 | """ 60 | Given an SSH config entry and the location of the SSH config, check to see if 61 | the current deployment has an entry, if it does overwrite it, else add it. If 62 | the SSH config does not exist at the provided location, create it first then 63 | write to it 64 | :param ssh_config_entry: str Representing the SSH config entry 65 | :param config_location: str Representing the full path to the SSH config 66 | :param config_location_dir: str Representing the directory path where the SSH config 67 | is 68 | """ 69 | if not os.path.exists(config_location): 70 | Path(config_location_dir).mkdir(parents=True, exist_ok=True) 71 | with open(config_location, "w"): 72 | pass 73 | file_content = "" 74 | else: 75 | with open(config_location, "r+") as config_file: 76 | file_content = config_file.read() 77 | if re.findall(SSH_CONFIG_ENTRY_REGEX, file_content, re.MULTILINE): 78 | with open(config_location, "r+") as config_file: 79 | file_content = re.sub( 80 | SSH_CONFIG_ENTRY_REGEX, ssh_config_entry, file_content 81 | ) 82 | config_file.seek(0) 83 | config_file.write(file_content) 84 | config_file.truncate() 85 | else: 86 | with open(config_location, "a") as config_file: 87 | config_file.write(f"{ssh_config_entry}") 88 | print(f"SSH config entry has been written to: {config_location}") 89 | 90 | 91 | def write_config_entry(ssh_config_entry: str, ssh_config_location: str = None): 92 | """ 93 | Orchestrates find_previous_config_entry_and_replace_or_write_entry_if_not_present, 94 | if a config location is not provided, the location is hard coded to ./.ssh/config 95 | :param ssh_config_entry: str Representing the SSH config entry 96 | :param ssh_config_location: str Representing the full path to the SSH config 97 | """ 98 | if config_location := ssh_config_location: 99 | config_location_dir = os.path.dirname(config_location) 100 | find_previous_config_entry_and_replace_or_write_entry_if_not_present( 101 | ssh_config_entry, config_location, config_location_dir 102 | ) 103 | else: 104 | config_location = "./.ssh/config" 105 | config_location_dir = "./.ssh" 106 | find_previous_config_entry_and_replace_or_write_entry_if_not_present( 107 | ssh_config_entry, config_location, config_location_dir 108 | ) 109 | 110 | 111 | if __name__ == "__main__": 112 | """ 113 | Orchestrates the retrieval of the remote workstations IP and writing an 114 | SSH config entry for it 115 | """ 116 | if instance_ip := get_instance_ip(): 117 | ssh_config_entry = generate_ssh_config_entry(instance_ip) 118 | if ssh_config_location := os.environ.get("SSH_CONFIG_LOCATION"): 119 | write_config_entry(ssh_config_entry, ssh_config_location) 120 | else: 121 | write_config_entry(ssh_config_entry) 122 | else: 123 | print("No instance found, no SSH config entry to be made") 124 | --------------------------------------------------------------------------------