diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..c5e6d3e --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +npm_modules/ +dist/ +cdk.out/ \ No newline at end of file diff --git a/.github/workflows/test-cdk.yml b/.github/workflows/test-cdk.yml new file mode 100644 index 0000000..308eb8e --- /dev/null +++ b/.github/workflows/test-cdk.yml @@ -0,0 +1,28 @@ +name: Test CDK +on: + workflow_dispatch: + push: + branches: ["**"] + paths: ["lib/**"] + pull_request: + branches: [master] + paths: ["lib/**"] +jobs: + Run-CDK-Tests: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [16.x, 18.x, 20.x] + + steps: + - name: Check out repository code + uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci + - run: npm run check + - run: npm run build --if-present + - run: npm test \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1ebeae5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.js +!jest.config.js +*.d.ts +node_modules +dist + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..c1d6d45 --- /dev/null +++ b/.npmignore @@ -0,0 +1,6 @@ +*.ts +!*.d.ts + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..296ce55 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Amazon.com, Inc. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..eac53b6 --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# aws4embeddedlinux-build + +This repository contains [buildbot](https://buildbot.net/) using [AWS CDK](https://aws.amazon.com/cdk/) to run [Yocto](https://www.yoctoproject.org/) embedded Linux build jobs in AWS. + + +## Code structure + +This is a standard CDK project. + +The main stack definition can be found in [`lib/app.ts`](lib/app.ts). + + +## Requirements and deployment + +In order to be able to deploy this CDK project you need to have the following: + + - An AWS account + - The [AWS CLI](https://aws.amazon.com/cli/) installed and configured in your development machine + - [AWS CDK](https://aws.amazon.com/cdk/) installed and configured in your development machine + - Node.js and npm installed + +Now, in order to deploy this CDK project to your AWS account, you simply have to clone this repository and from the root folder of the project run: + +```bash +npm install +``` + +To install the necessary dependencies, and then: + +```bash +npm run build +cdk deploy +``` + +To synthesise and deploy the project stack. + +Then prompt `y` for yes. + +If you want to clean up your account you can delete this stack with: + +```bash +cdk destroy +``` + + +## Contributing + +Everyone is very welcome to contribute to this project. +You can contribute just by submitting bugs or suggesting improvements by +opening an issue on GitHub. + + +## License + +Licensed under [MIT License](LICENSE.md). \ No newline at end of file diff --git a/cdk.context.json b/cdk.context.json new file mode 100644 index 0000000..1b0519a --- /dev/null +++ b/cdk.context.json @@ -0,0 +1,3 @@ +{ + "availability-zones:account=743600277648:region=eu-central-1": ["eu-central-1a", "eu-central-1b", "eu-central-1c"] +} diff --git a/cdk.json b/cdk.json new file mode 100644 index 0000000..168a20a --- /dev/null +++ b/cdk.json @@ -0,0 +1,10 @@ +{ + "app": "node dist/lib/app", + "amzn": { + "cdk-build": { + "build": "npm-pretty-much", + "version": 1 + } + }, + "output": "dist/cdk.out" +} diff --git a/configuration/admin/.dockerignore b/configuration/admin/.dockerignore new file mode 100644 index 0000000..c4c4ffc --- /dev/null +++ b/configuration/admin/.dockerignore @@ -0,0 +1 @@ +*.zip diff --git a/configuration/admin/.gitignore b/configuration/admin/.gitignore new file mode 100644 index 0000000..5a6cf99 --- /dev/null +++ b/configuration/admin/.gitignore @@ -0,0 +1,2 @@ +*.zip +venv/ diff --git a/configuration/admin/Dockerfile b/configuration/admin/Dockerfile new file mode 100644 index 0000000..7057c1e --- /dev/null +++ b/configuration/admin/Dockerfile @@ -0,0 +1,33 @@ +FROM public.ecr.aws/lts/ubuntu:22.04_stable + +RUN apt-get update && apt-get install -y build-essential python3-dev libssl-dev \ + libffi-dev python3-pip python3-venv git + +# DEBUG +RUN apt-get install -y psmisc vim lsof nmap iputils-ping curl +# DEBUG + +RUN useradd -U -m user + +USER user +WORKDIR /home/user + +EXPOSE 8010 +EXPOSE 9989 + +RUN python3 -m pip install --upgrade pip +RUN python3 -m pip install boto3 botocore +RUN python3 -m pip install git-remote-codecommit +RUN python3 -m pip install 'buildbot[bundle]' + +ENV PATH="/home/user/.local/bin:${PATH}" + +RUN buildbot create-master . +COPY admin.cfg . +COPY envfile . +COPY entry.sh . +RUN mkdir userconfig +RUN chown -R user: userconfiguration/ +COPY user.cfg userconfiguration/ + +ENTRYPOINT [ "./entry.sh" ] diff --git a/configuration/admin/README.md b/configuration/admin/README.md new file mode 100644 index 0000000..09022fa --- /dev/null +++ b/configuration/admin/README.md @@ -0,0 +1,27 @@ +Setting up: + +* Deploy source bucket and pipeline then use the bucket name below. + +``` +# Zip the needed files. +zip -o config.zip buildspec.yml Dockerfile entry.sh master.cfg + +# S3 CP to the config key. +aws --profile PROFILE s3 cp config.zip s3://BUCKET/config +``` + +Running locally: +``` +docker build -t buildbot:latest . + +# Does not include worker port, found in the master.cfg +docker run -p 8010:8010 buildbot:latest +``` + +Development Setup +``` +python3 -m venv venv +. ./venv/bin/activate +pip install -r dev-requirements.txt +``` + diff --git a/configuration/admin/admin.cfg b/configuration/admin/admin.cfg new file mode 100644 index 0000000..bfcb453 --- /dev/null +++ b/configuration/admin/admin.cfg @@ -0,0 +1,320 @@ +# -*- python -*- +# ex: set filetype=python: + +import os + +from buildbot.plugins import * +from buildbot.plugins import worker + +# This is the dictionary that the buildmaster pays attention to. We also use +# a shorter alias to save typing. +c = BuildmasterConfig = {} + +####### WORKER init scripts +init_script = '''#!/bin/bash +exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1 + +sudo apt-get update +sudo apt install buildbot-worker xfsprogs nfs-common curl -y + +# install additional packages from yocto https://docs.yoctoproject.org/brief-yoctoprojectqs/index.html +sudo apt install gawk wget git diffstat unzip texinfo gcc build-essential chrpath socat cpio python3 python3-pip python3-pexpect xz-utils debianutils iputils-ping python3-git python3-jinja2 libegl1-mesa libsdl1.2-dev xterm python3-subunit mesa-common-dev zstd liblz4-tool -y + + +# mount sstate volume +sudo mkdir /sstate +sudo mount -t nfs -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport {}:/ /sstate +sudo chown ubuntu: /sstate + + +# mount data volume +# nvme1n1 is default for nitro based instance types, regardless if you configure /dev/xvdf +if [ -b /dev/nvme1n1 ]; then + FILE=/dev/nvme1n1 +elif [ -b /dev/xvdf ]; then + FILE=/dev/xvdf +else + echo "No EBS device exists. Not enough space to build something" + exit 1 +fi + +sudo mkdir /data +sudo mkfs -t xfs $FILE +sudo mount $FILE /data + +cd /data +sudo mkdir buildbot +sudo chown ubuntu: buildbot +cd buildbot +sudo -u ubuntu bash -c 'buildbot-worker create-worker worker buildbot.service:9989 {} sekrit' +sudo sh -c "cat </etc/systemd/system/buildbot-worker.service +[Unit] +Description=Buildbot Worker +After=network.target +[Service] +User=ubuntu +Group=ubuntu +WorkingDirectory=/data/buildbot/ +ExecStart=/bin/buildbot-worker start --nodaemon worker +# if using EC2 Latent worker, you want to uncomment following line, and comment out the Restart line +ExecStopPost=sudo shutdown now +Restart=no +[Install] +WantedBy=multi-user.target +EOM" +echo "TODO: aws4embeddedlinux" > /data/buildbot/worker/info/admin +echo "TODO: host info" > /data/buildbot/worker/info/host +sudo systemctl enable buildbot-worker.service +sudo systemctl start buildbot-worker.service +''' + +c['workers'] = [] +c['change_source'] = [] +c['schedulers'] = [] +c['builders'] = [] + +if 'BUILDBOT_MQ_URL' in os.environ: + c['mq'] = { + 'type' : 'wamp', + 'router_url': os.environ['BUILDBOT_MQ_URL'], + 'realm': os.environ.get('BUILDBOT_MQ_REALM', 'buildbot').decode('utf-8'), + 'debug' : 'BUILDBOT_MQ_DEBUG' in os.environ, + 'debug_websockets' : 'BUILDBOT_MQ_DEBUG' in os.environ, + 'debug_lowlevel' : 'BUILDBOT_MQ_DEBUG' in os.environ, + } +# 'protocols' contains information about protocols which master will use for +# communicating with workers. You must define at least 'port' option that workers +# could connect to your master with this protocol. +# 'port' must match the value configured into the workers nvme1 + +####reload user.cfg factory +reload_config_factory = util.BuildFactory() +reload_config_factory.addStep(steps.MasterShellCommand(command="git clone " + os.environ.get('CODECOMMIT_REPOSITORY_CLONE_URL_GRC', 'default') + " && rm -rf userconfiguration/* && cp buildbot-user-repo/* userconfiguration/ && rm -rf buildbot-user-repo && cat admin.cfg > master.cfg && cat userconfiguration/user.cfg >> master.cfg", env={'PATH': ["/home/user","${PATH}"]}, name="checkout changed configuration", description=" user.py is stored in a codecommit repo")) +reload_config_factory.addStep(steps.MasterShellCommand(command="killall -SIGHUP python3", name="reload buildbot configuration", description="will send a SIGHUP to buildbot process")) + +c['workers'].append(worker.LocalWorker('bot1')) + +c['builders'].append(util.BuilderConfig(name="reload_config_factory",workernames=["bot1"],factory=reload_config_factory)) + +c['change_source'].append(changes.GitPoller( + os.environ.get('CODECOMMIT_REPOSITORY_CLONE_URL_GRC', 'default'), + workdir='gitpoller-workdir', branch='main', + pollinterval=60)) + +c['schedulers'].append(schedulers.SingleBranchScheduler( + name="tree_stable_reload_buildbot_configuration", + change_filter=util.ChangeFilter(branch="main",repository=os.environ.get('CODECOMMIT_REPOSITORY_CLONE_URL_GRC', 'default')), + treeStableTimer=60*2, + builderNames=["reload_config_factory"])) + +####### REPORTER TARGETS + +# 'services' is a list of Reporter Targets. The results of each build will be +# pushed to these targets. buildbot/reporters/*.py has a variety to choose from, +# like IRC bots. + +c['services'] = [] + +####### PROJECT IDENTITY + +# the 'title' string will appear at the top of this buildbot installation's +# home pages (linked to the 'titleURL'). + +c['title'] = "aws4embeddedlinux buildbot" +c['titleURL'] = "aws4embeddedlinux buildbot" + +# the 'buildbotURL' string should point to the location where the buildbot's +# internal web server is visible. This typically uses the port number set in +# the 'www' entry below, but with an externally-visible host name which the +# buildbot cannot figure out without some help. + +weburl = os.environ.get("BUILDBOT_WEB_URL", "default") +c['buildbotURL'] = weburl.lower() + "/" + +# minimalistic config to activate new web UI +c['www'] = dict(port=os.environ.get("BUILDBOT_WEB_PORT", 8010), + plugins=dict(waterfall_view={}, console_view={},)) + +# GitHub webhook receiver +c['www']['change_hook_dialects'] = {'github': {}} + +####### DB URL + +c['db'] = { + "db_url": os.environ.get("BUILDBOT_DB_URL", "sqlite:////mount/data/buildbot.db"), +} + +c['protocols'] = {'pb': {'port': 9989}} + +# send usage data +c['buildbotNetUsageData'] = None + + +#################################################################################### +# +# this should be in user.py +# +#################################################################################### + +x86_64_ami = 'ami-0ee3d9a8776e8b99c' +arm64_ami = 'ami-0817b9078575a7d59' + + +####### INSTANCE TEST +factory2 = util.BuildFactory() +factory2.addStep(steps.ShellCommand(command="lsblk && mount", name="debug info", description="check the EBS / EFS")) +factory2.addStep(steps.Git(repourl='https://github.com/thomas-roos/yocto_example.git', branch="test_sstate", submodules=True, name="checkout source")) +factory2.addStep(steps.ShellCommand(command="./download.sh", name="download", description="download all sources")) +factory2.addStep(steps.ShellCommand(command="./build.sh", name="build", description="build test")) +factory2.addStep(steps.ShellCommand(command="./test.sh", name="test", description="do ptest")) + +x86_64_instance_types = ["m6i.metal", "c6a.8xlarge", "m6i.8xlarge","m6in.16xlarge","m6a.24xlarge","m6i.24xlarge","c6i.24xlarge","c6a.24xlarge"] +arm_64_instance_types = ["c6g.metal", "c7g.metal", "c6g.16xlarge","c7g.16xlarge"] + +sstate_efs_dns_name = os.environ.get('BUILDBOT_WORKER_SSTATE_EFS_FS_ID', 'default')+".efs."+ os.environ.get('AWS_REGION', 'default') + ".amazonaws.com" + +for instance in x86_64_instance_types: + c['workers'].append(worker.EC2LatentWorker(instance.replace(".", "_"), 'sekrit', instance, + ami=x86_64_ami, + build_wait_timeout=0, + keypair_name='worker-key', + security_group_ids=[os.environ.get('BUILDBOT_WORKER_SECURITY_GROUP', 'default'),], + subnet_id=os.environ.get('BUILDBOT_WORKER_SUBNET', 'default'), + user_data=init_script.format(sstate_efs_dns_name, instance.replace(".", "_")), + block_device_map= [ + { + "DeviceName": "/dev/xvdf", + "VirtualName":"data", + "Ebs" : { + "VolumeType": "gp3", + "Iops": 16000, + "VolumeSize": 100, + "Throughput": 1000, + } + } + ] + )) + +for instance in arm_64_instance_types: + c['workers'].append(worker.EC2LatentWorker(instance.replace(".", "_"), 'sekrit', instance, + ami=arm64_ami, + build_wait_timeout=0, + keypair_name='worker-key', + security_group_ids=[os.environ.get('BUILDBOT_WORKER_SECURITY_GROUP', 'default'),], + subnet_id=os.environ.get('BUILDBOT_WORKER_SUBNET', 'default'), + user_data=init_script.format(sstate_efs_dns_name, instance.replace(".", "_")), + block_device_map= [ + { + "DeviceName": "/dev/xvdf", + "VirtualName":"data", + "Ebs" : { + "VolumeType": "gp3", + "Iops": 16000, + "VolumeSize": 100, + "Throughput": 1000, + } + } + ])) + +for instance in x86_64_instance_types + arm_64_instance_types: + c['schedulers'].append(schedulers.ForceScheduler( + name=instance.replace(".", "_"), + builderNames=[instance.replace(".", "_")])) + + c['builders'].append( + util.BuilderConfig(name=instance.replace(".", "_"), + workernames=[instance.replace(".", "_")], + factory=factory2)) + + +##### meta-aws release tests ####################################################################################################### +meta_aws_supported_releases = ["master", "kirkstone", "dunfell", "mickledore"] +meta_aws_supported_archs = ["qemux86-64", "qemuarm64"] + +for release in meta_aws_supported_releases: + for arch in meta_aws_supported_archs: + meta_aws_release_factory = util.BuildFactory() + meta_aws_release_factory.addStep(steps.Git(repourl='https://github.com/aws4embeddedlinux/meta-aws-ci.git', mode='full', submodules=True)) + meta_aws_release_factory.addStep(steps.ShellCommand(command='cd release-tests/ && BB_ENV_PASSTHROUGH_ADDITIONS="SSTATE_DIR $BB_ENV_PASSTHROUGH_ADDITIONS" SSTATE_DIR="/sstate" ./meta-aws-release-tests.sh ' + release + ' ' + '"' + arch +'"')) + + workername = "meta_aws_release_worker_" + release + "_" + arch.replace("-", "_") + c['workers'].append(worker.EC2LatentWorker(workername, 'sekrit', 'c7g.16xlarge', + ami=arm64_ami, + build_wait_timeout=0, + keypair_name='worker-key', + security_group_ids=[os.environ.get('BUILDBOT_WORKER_SECURITY_GROUP', 'default'),], + subnet_id=os.environ.get('BUILDBOT_WORKER_SUBNET', 'default'), + user_data=init_script.format(sstate_efs_dns_name, workername), + block_device_map= [ + { + "DeviceName": "/dev/xvdf", + "Ebs" : { + "VolumeType": "gp3", + "Iops": 16000, + "VolumeSize": 500, + "Throughput": 1000, + } + } + ], + )) + + buildername = "meta_aws_release_builder_" + release + "_" + arch.replace("-", "_") + c['builders'].append( + util.BuilderConfig(name=buildername, + workernames=[workername], + factory=meta_aws_release_factory)) + + schedulername = "meta-aws_release_tests_" + release + "_" + arch.replace("-", "_") + c['schedulers'].append( + schedulers.ForceScheduler( + name=schedulername, + builderNames=[buildername], + buttonName="force")) + + c['schedulers'].append( + schedulers.Nightly(name="weekly-meta_aws_release_builder_" + release + "_" + arch.replace("-", "_"), + builderNames=[buildername], + dayOfWeek=6, hour=0, minute=1)) + + +##### meta-aws-demos tests ####################################################################################################### +meta_aws_release_factory = util.BuildFactory() +meta_aws_release_factory.addStep(steps.Git(repourl='https://github.com/aws4embeddedlinux/meta-aws-demos.git', mode='full', submodules=True)) +meta_aws_release_factory.addStep(steps.ShellCommand(command=['/bin/bash', '-c', '''. init-build-env && BB_ENV_PASSTHROUGH_ADDITIONS="SSTATE_DIR $BB_ENV_PASSTHROUGH_ADDITIONS" SSTATE_DIR="/sstate" && for d in $(get_devices); do BUILD_DEVICE=$d bitbake aws-greengrass-test-image; done '''])) +workername = "meta_aws_demos_release_worker" +c['workers'].append(worker.EC2LatentWorker(workername, 'sekrit', 'c7g.16xlarge', + ami=arm64_ami, + build_wait_timeout=0, + keypair_name='worker-key', + security_group_ids=[os.environ.get('BUILDBOT_WORKER_SECURITY_GROUP', 'default'),], + subnet_id=os.environ.get('BUILDBOT_WORKER_SUBNET', 'default'), + user_data=init_script.format(sstate_efs_dns_name, workername), + block_device_map= [ + { + "DeviceName": "/dev/xvdf", + "Ebs" : { + "VolumeType": "gp3", + "Iops": 16000, + "VolumeSize": 500, + "Throughput": 1000, + } + } + ], + )) +buildername = "meta_aws_demos_release_builder" +c['builders'].append( + util.BuilderConfig(name=buildername, + workernames=[workername], + factory=meta_aws_release_factory)) +schedulername = "meta-aws_demos_release_tests" +c['schedulers'].append( + schedulers.ForceScheduler( + name=schedulername, + builderNames=[buildername], + buttonName="force")) +c['schedulers'].append( + schedulers.Nightly(name="weekly-meta_aws_demos_release_builder", + builderNames=[buildername], + dayOfWeek=6, hour=0, minute=1)) + +#### user.cfg gets pasted at the end of this file #### diff --git a/configuration/admin/buildspec.yml b/configuration/admin/buildspec.yml new file mode 100644 index 0000000..74b5bd3 --- /dev/null +++ b/configuration/admin/buildspec.yml @@ -0,0 +1,23 @@ +version: 0.2 +phases: + install: + commands: + - pip install git-remote-codecommit + pre_build: + commands: + - aws ecr get-login-password | docker login --username AWS --password-stdin $ECR_REPOSITORY_URI + build: + commands: + - echo Built on `date` + - git clone $CODECOMMIT_REPOSITORY_CLONE_URL_GRC && cp buildbot-user-repo/user.cfg . + - echo BUILDBOT_WORKER_SECURITY_GROUP=$BUILDBOT_WORKER_SECURITY_GROUP >> envfile + - echo BUILDBOT_WORKER_SUBNET=$BUILDBOT_WORKER_SUBNET >> envfile + - echo CODECOMMIT_REPOSITORY_CLONE_URL_GRC=$CODECOMMIT_REPOSITORY_CLONE_URL_GRC >> envfile + - echo BUILDBOT_WEB_URL=$BUILDBOT_WEB_URL >> envfile + - echo BUILDBOT_WORKER_SSTATE_EFS_FS_ID=$BUILDBOT_WORKER_SSTATE_EFS_FS_ID >> envfile + - cat envfile + - docker build -t $ECR_REPOSITORY_URI:latest . + - docker push $ECR_REPOSITORY_URI:latest + - printf '[{"name":"buildbot-server","imageUri":"%s"}]' $ECR_REPOSITORY_URI:latest > imagedefinitions.json +artifacts: + files: imagedefinitions.json diff --git a/configuration/admin/dev-requirements.txt b/configuration/admin/dev-requirements.txt new file mode 100644 index 0000000..4468494 --- /dev/null +++ b/configuration/admin/dev-requirements.txt @@ -0,0 +1,41 @@ +alembic==1.9.4 +attrs==22.2.0 +autobahn==23.1.2 +Automat==22.10.0 +black==23.1.0 +boto3==1.26.74 +botocore==1.29.74 +buildbot==3.7.0 +buildbot-worker==3.7.0 +cffi==1.15.1 +click==8.1.3 +constantly==15.1.0 +cryptography==39.0.1 +future==0.18.3 +greenlet==2.0.2 +hyperlink==21.0.0 +idna==3.4 +incremental==22.10.0 +isort==5.12.0 +Jinja2==3.1.2 +jmespath==1.0.1 +Mako==1.2.4 +MarkupSafe==2.1.2 +msgpack==1.0.4 +mypy-extensions==1.0.0 +packaging==23.0 +pathspec==0.11.0 +platformdirs==3.0.0 +pycparser==2.21 +PyJWT==2.6.0 +python-dateutil==2.8.2 +PyYAML==6.0 +s3transfer==0.6.0 +six==1.16.0 +SQLAlchemy==1.4.46 +tomli==2.0.1 +Twisted==22.10.0 +txaio==23.1.1 +typing_extensions==4.5.0 +urllib3==1.26.14 +zope.interface==5.5.2 diff --git a/configuration/admin/entry.sh b/configuration/admin/entry.sh new file mode 100755 index 0000000..93945d7 --- /dev/null +++ b/configuration/admin/entry.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +export $(xargs < envfile) +cat envfile + +cat admin.cfg > master.cfg +cat userconfiguration/user.cfg >> master.cfg + +ls -la /mount/data/* +buildbot upgrade-master /home/user +buildbot start . +tail -f twistd.log diff --git a/configuration/user-repo/user.cfg b/configuration/user-repo/user.cfg new file mode 100644 index 0000000..19efb24 --- /dev/null +++ b/configuration/user-repo/user.cfg @@ -0,0 +1,50 @@ +### user.cfg ### + + +#### core-image-minimal example ###################### +c['change_source'].append(changes.GitPoller( + "https://github.com/yoctoproject/poky.git", + workdir='gitpoller-workdir', branch='master-next', + pollinterval=60)) + +c['schedulers'].append(schedulers.SingleBranchScheduler( + name="check_poky", + change_filter=util.ChangeFilter(branch="master-next",repository="https://github.com/yoctoproject/poky.git"), + treeStableTimer=60*2, + builderNames=["core_image_minimal_builder_c7g_16xlarge"])) + +core_image_minimal_factory = util.BuildFactory() + +core_image_minimal_factory.addStep(steps.Git(repourl='https://github.com/yoctoproject/poky.git')) +core_image_minimal_factory.addStep(steps.ShellCommand(command=['/bin/bash', '-c', '''source oe-init-build-env ; echo 'SSTATE_DIR = \"/sstate\"' > conf/site.conf ; bitbake core-image-minimal'''])) + +c['workers'].append(worker.EC2LatentWorker("core_image_minimal_worker_c7g_16xlarge", 'sekrit', 'c7g.16xlarge', + ami=arm64_ami, + build_wait_timeout=0, + keypair_name='worker-key', + security_group_ids=[os.environ.get('BUILDBOT_WORKER_SECURITY_GROUP', 'default'),], + subnet_id=os.environ.get('BUILDBOT_WORKER_SUBNET', 'default'), + user_data=init_script.format(sstate_efs_dns_name, 'core_image_minimal_worker_c7g_16xlarge'), + block_device_map= [ + { + "DeviceName": "/dev/xvdf", + "Ebs" : { + "VolumeType": "gp3", + "Iops": 16000, + "VolumeSize": 500, + "Throughput": 1000, + } + } + ], + )) + +c['builders'].append( + util.BuilderConfig(name="core_image_minimal_builder_c7g_16xlarge", + workernames=["core_image_minimal_worker_c7g_16xlarge"], + factory=core_image_minimal_factory)) + +c['schedulers'].append( + schedulers.ForceScheduler( + name="core_image_minimal_c7g_16xlarge", + builderNames=["core_image_minimal_builder_c7g_16xlarge"], + buttonName="force")) \ No newline at end of file diff --git a/lib/app.ts b/lib/app.ts new file mode 100644 index 0000000..560fce1 --- /dev/null +++ b/lib/app.ts @@ -0,0 +1,114 @@ +#!/usr/bin/env node +import { App } from 'aws-cdk-lib'; + +import { CacheRepoStack, CachePipeline, CacheEcrStack, CacheEcsStack } from './cache-server'; +import { BuildBotConfig, BuildBotImageRepo, BuildBotPipeline, BuildBotServer, BuildBotFilesystem } from './buildbot'; +import { DeveloperStages, FederateOIDC } from './constants'; +import { Vpc, Cluster, ServiceDomain } from './stacks'; + +// Set up your CDK App +const app = new App(); + +const dev = true; + +if (dev) { + const devName = process.env.USER || ''; + + const stageProps = DeveloperStages[devName]; + + const env = { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }; + + const terminationProtection = true; + /// App stacks + + const vpc = new Vpc(app, 'Vpc', { + env, + terminationProtection, + }); + + const cluster = new Cluster(app, 'Cluster', { + vpc: vpc.vpc, + env, + terminationProtection, + }); + + const serviceDomain = new ServiceDomain(app, 'ServiceNamespace', { + vpc: vpc.vpc, + env, + terminationProtection, + }); + + const ssr = new CacheRepoStack(app, 'CacheRepo-Personal', { + env, + terminationProtection, + }); + + const ecr = new CacheEcrStack(app, 'CacheEcr-Personal', { + env, + terminationProtection, + }); + + const ecs = new CacheEcsStack(app, 'CacheEcs-Personal', { + env, + terminationProtection, + repository: ecr.repo, + cluster: cluster.cluster, + vpc: vpc.vpc, + namespace: serviceDomain.namespace, + }); + + const buildBotConfig = new BuildBotConfig(app, 'BuildBotConfig-Personal', { + env, + terminationProtection, + }); + + const buildBotImageRepo = new BuildBotImageRepo(app, 'BuildBotImageRepo-Personal', { + env, + terminationProtection, + }); + const buildBotFilesystem = new BuildBotFilesystem(app, 'BuildBotFilesystem-Personal', { + env, + terminationProtection, + vpc: vpc.vpc, + }); + + const buildBotServer = new BuildBotServer(app, 'BuildBotServer-Personal', { + env, + terminationProtection, + vpc: vpc.vpc, + cluster: cluster.cluster, + serviceName: 'buildbot', + imageRepository: buildBotImageRepo.repo, + keypair: 'buildbot-key', + filesystem: buildBotFilesystem.filesystem, + accesspoint: buildBotFilesystem.accesspoint, + useWebhookAPI: true, + namespace: serviceDomain.namespace, + oidcAction: FederateOIDC, + }); + + new BuildBotPipeline(app, 'BuildBotPipeline-Personal', { + env, + terminationProtection, + bucket: buildBotConfig.bucket, + bucketKey: 'admin-configuration/config.zip', + service: buildBotServer.service, + repo: buildBotImageRepo.repo, + configrepo: buildBotConfig.configrepo, + workersg: buildBotServer.workerSecurityGroup.securityGroupId, + workerVpcSubnetID: buildBotServer.workerVpcSubnetID, + loadBalancerDNS: buildBotServer.loadBalancerDNS, + buildBotServerPrivateDNS: buildBotServer.buildBotServerPrivateDNS, + workerSstateEfsFsID: buildBotServer.sstateFS.fileSystemId, + }); + + new CachePipeline(app, 'CachePipeline-Personal', { + env, + terminationProtection, + repository: ecr.repo, + cacheRepo: ssr.repo.repositoryName, + ecsCacheFargateService: ecs.service, + }); +} + +// End diff --git a/lib/buildbot/buildbot-config.ts b/lib/buildbot/buildbot-config.ts new file mode 100644 index 0000000..59ee9f5 --- /dev/null +++ b/lib/buildbot/buildbot-config.ts @@ -0,0 +1,59 @@ +import * as cdk from 'aws-cdk-lib'; +import { IBucket } from 'aws-cdk-lib/aws-s3'; +import { Bucket } from '../constructs'; +import { Code, Repository } from 'aws-cdk-lib/aws-codecommit'; +import { BucketDeployment, Source } from 'aws-cdk-lib/aws-s3-deployment'; +import { ManagedPolicy, PolicyStatement, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam'; + +export interface BuildBotConfigProps extends cdk.StackProps { + readonly bucketName?: string; +} + +export class BuildBotConfig extends cdk.Stack { + readonly bucket: IBucket; + readonly configrepo: Repository; + + constructor(scope: cdk.App, id: string, props: BuildBotConfigProps) { + super(scope, id, { ...props }); + + const configBucket = new Bucket(this, 'ConfigBucket', { + bucketName: props.bucketName, + }); + + const bucketDeploymentRole = new Role(this, 'BucketDeploymentRole', { + assumedBy: new ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')], + }); + + // TODO(glimsdal): Assert this instead of `if`. + if (props.env) { + const region = props.env.region; + const account = props.env.account; + + // Give the asset upload lambda the ability to use the bucket. + bucketDeploymentRole.addToPolicy( + new PolicyStatement({ + actions: ['kms:Decrypt'], + resources: [`arn:aws:kms:${region}:${account}:key/*`], + }), + ); + } + + new BucketDeployment(this, 'Deployment', { + sources: [Source.asset('dist/admin-config')], + destinationBucket: configBucket.bucket, + destinationKeyPrefix: 'admin-config', + role: bucketDeploymentRole, + extract: true, + }); + + this.bucket = configBucket.bucket; + + const repository = new Repository(this, 'Repository', { + repositoryName: 'buildbot-user-repo', + code: Code.fromDirectory('configuration/user-repo', 'main'), + }); + + this.configrepo = repository; + } +} diff --git a/lib/buildbot/buildbot-data.ts b/lib/buildbot/buildbot-data.ts new file mode 100644 index 0000000..42eecba --- /dev/null +++ b/lib/buildbot/buildbot-data.ts @@ -0,0 +1,35 @@ +import * as cdk from 'aws-cdk-lib'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as efs from 'aws-cdk-lib/aws-efs'; + +export interface BuildBotFilesystemProps extends cdk.StackProps { + readonly vpc: ec2.IVpc; +} + +export class BuildBotFilesystem extends cdk.Stack { + readonly filesystem: efs.IFileSystem; + readonly accesspoint: efs.IAccessPoint; + + constructor(scope: cdk.App, id: string, props: BuildBotFilesystemProps) { + super(scope, id, { ...props }); + + const filesystem = new efs.FileSystem(this, 'FileSystem', { + vpc: props.vpc, + }); + + this.accesspoint = filesystem.addAccessPoint('AccessPoint', { + path: '/buildbot-data', + posixUser: { + uid: '1000', + gid: '1000', + }, + createAcl: { + ownerGid: '1000', + ownerUid: '1000', + permissions: '755', + }, + }); + + this.filesystem = filesystem; + } +} diff --git a/lib/buildbot/buildbot-image-repo.ts b/lib/buildbot/buildbot-image-repo.ts new file mode 100644 index 0000000..a4053a2 --- /dev/null +++ b/lib/buildbot/buildbot-image-repo.ts @@ -0,0 +1,14 @@ +import * as cdk from 'aws-cdk-lib'; +import { IRepository, Repository } from 'aws-cdk-lib/aws-ecr'; + +export type BuildBotImageRepoProps = cdk.StackProps; + +export class BuildBotImageRepo extends cdk.Stack { + readonly repo: IRepository; + + constructor(scope: cdk.App, id: string, props: BuildBotImageRepoProps) { + super(scope, id, { ...props }); + + this.repo = new Repository(this, 'BuildBotServerECR', {}); + } +} diff --git a/lib/buildbot/buildbot-pipeline.ts b/lib/buildbot/buildbot-pipeline.ts new file mode 100644 index 0000000..f7f58a9 --- /dev/null +++ b/lib/buildbot/buildbot-pipeline.ts @@ -0,0 +1,102 @@ +import * as cdk from 'aws-cdk-lib'; +import { BuildEnvironmentVariableType, BuildSpec, LinuxBuildImage, PipelineProject } from 'aws-cdk-lib/aws-codebuild'; +import { Artifact, Pipeline } from 'aws-cdk-lib/aws-codepipeline'; +import { CodeBuildAction, EcsDeployAction, S3SourceAction } from 'aws-cdk-lib/aws-codepipeline-actions'; +import { IRepository } from 'aws-cdk-lib/aws-ecr'; +import { Repository } from 'aws-cdk-lib/aws-codecommit'; +import { IBaseService } from 'aws-cdk-lib/aws-ecs'; +import { IBucket } from 'aws-cdk-lib/aws-s3'; + +export interface BuildBotPipelineProps extends cdk.StackProps { + readonly bucket: IBucket; + readonly bucketKey: string; + readonly service: IBaseService; + readonly repo: IRepository; + readonly configrepo: Repository; + readonly workersg: string; + readonly loadBalancerDNS: string; + readonly buildBotServerPrivateDNS: string; + readonly workerVpcSubnetID: string; + readonly workerSstateEfsFsID: string; +} + +export class BuildBotPipeline extends cdk.Stack { + constructor(scope: cdk.App, id: string, props: BuildBotPipelineProps) { + super(scope, id, { ...props }); + const sourceOutput = new Artifact('BuildBotSourceArtifact'); + const sourceAction = new S3SourceAction({ + actionName: 'BuildBot-Source', + output: sourceOutput, + bucket: props.bucket, + bucketKey: props.bucketKey, + }); + + const buildBotBuildProject = new PipelineProject(this, 'BuildBotBuildProject', { + buildSpec: BuildSpec.fromSourceFilename('buildspec.yml'), + environment: { + privileged: true, + buildImage: LinuxBuildImage.STANDARD_6_0, + environmentVariables: { + ECR_REPOSITORY_URI: { + type: BuildEnvironmentVariableType.PLAINTEXT, + value: props.repo.repositoryUri, + }, + CODECOMMIT_REPOSITORY_CLONE_URL_GRC: { + type: BuildEnvironmentVariableType.PLAINTEXT, + value: props.configrepo.repositoryCloneUrlGrc, + }, + BUILDBOT_WORKER_SECURITY_GROUP: { + type: BuildEnvironmentVariableType.PLAINTEXT, + value: props.workersg, + }, + BUILDBOT_WORKER_SUBNET: { + type: BuildEnvironmentVariableType.PLAINTEXT, + value: props.workerVpcSubnetID, + }, + BUILDBOT_WEB_URL: { + type: BuildEnvironmentVariableType.PLAINTEXT, + value: props.loadBalancerDNS, + }, + BUILDBOT_WORKER_SSTATE_EFS_FS_ID: { + type: BuildEnvironmentVariableType.PLAINTEXT, + value: props.workerSstateEfsFsID, + }, + }, + }, + }); + props.repo.grantPullPush(buildBotBuildProject); + props.configrepo.grantPull(buildBotBuildProject); + + const buildOutput = new Artifact('BuildBotBuildArtifact'); + const buildAction = new CodeBuildAction({ + actionName: 'BuildBot-Build', + project: buildBotBuildProject, + input: sourceOutput, + outputs: [buildOutput], + }); + const deployAction = new EcsDeployAction({ + actionName: 'BuildBot-Deploy', + service: props.service, + input: buildOutput, + }); + + const pipeline = new Pipeline(this, 'BuildBotDeployPipeline', { + pipelineName: 'BuildBot-Image-Deploy', + stages: [ + { + stageName: 'Source', + actions: [sourceAction], + }, + { + stageName: 'Build', + actions: [buildAction], + }, + { + stageName: 'Deploy', + actions: [deployAction], + }, + ], + }); + props.bucket.grantRead(pipeline.role); + } +} diff --git a/lib/buildbot/buildbot-server.ts b/lib/buildbot/buildbot-server.ts new file mode 100644 index 0000000..1272640 --- /dev/null +++ b/lib/buildbot/buildbot-server.ts @@ -0,0 +1,313 @@ +import * as cdk from 'aws-cdk-lib'; +import { CfnOutput } from 'aws-cdk-lib'; +import { ISecurityGroup, IVpc, Peer, Port, SecurityGroup } from 'aws-cdk-lib/aws-ec2'; +import { IRepository } from 'aws-cdk-lib/aws-ecr'; +import { IAccessPoint, IFileSystem } from 'aws-cdk-lib/aws-efs'; +import { FileSystem } from 'aws-cdk-lib/aws-efs'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import { + ApplicationListener, + ApplicationLoadBalancer, + ApplicationProtocol, + ListenerAction, +} from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { + CfnInstanceProfile, + ManagedPolicy, + PolicyStatement, + Effect, + Role, + ServicePrincipal, +} from 'aws-cdk-lib/aws-iam'; +import { IPrivateDnsNamespace } from 'aws-cdk-lib/aws-servicediscovery'; +import { Duration } from 'aws-cdk-lib'; +import { LoadBalancerTarget } from 'aws-cdk-lib/aws-route53-targets'; +import { Bucket } from 'aws-cdk-lib/aws-s3'; + +import * as acm from 'aws-cdk-lib/aws-certificatemanager'; +import * as route53 from 'aws-cdk-lib/aws-route53'; +import { WebhookAPI } from '../constructs'; + +export interface DNS { + readonly zone: route53.IHostedZone; + readonly cert: acm.ICertificate; +} + +export interface OidcAction { + readonly issuer: string; + readonly tokenEndpoint: string; + readonly userInfoEndpoint: string; + readonly authorizationEndpoint: string; + readonly clientId: string; + readonly clientSecret: cdk.SecretValue; +} + +export interface BuildBotServerProps extends cdk.StackProps { + readonly serviceName: string; + readonly imageRepository: IRepository; + readonly vpc: IVpc; + readonly cluster: ecs.ICluster; + readonly keypair: string; + readonly filesystem: IFileSystem; + readonly accesspoint: IAccessPoint; + readonly namespace: IPrivateDnsNamespace; + readonly dns?: DNS; + readonly useWebhookAPI?: boolean; + readonly oidcAction?: OidcAction; +} + +export class BuildBotServer extends cdk.Stack { + public readonly service: ecs.IBaseService; + public readonly workerSecurityGroup: ISecurityGroup; + // TODO: Rename to reflect DNS and cert + public readonly loadBalancerDNS: string; + public readonly buildBotServerPrivateDNS: string; + public readonly workerVpcSubnetID: string; + public readonly sstateFS: FileSystem; + + private securityGroups: ISecurityGroup[]; + + constructor(scope: cdk.App, id: string, props: BuildBotServerProps) { + super(scope, id, { ...props }); + + // Temporary SG to allow only Corp access to our insecure service. Later we will use OIDC. + const buildbotSg = new SecurityGroup(this, 'BuildBotCorpPrefix', { + vpc: props.vpc, + description: 'BuildBot Web Access From Corp.', + }); + + if (props.dns) { + buildbotSg.addIngressRule(Peer.anyIpv4(), Port.tcp(443)); + } else { + buildbotSg.addIngressRule(Peer.prefixList('pl-f8a64391'), Port.tcp(80)); + } + + // Set up a SG for worker nodes to use. + this.workerSecurityGroup = new SecurityGroup(this, 'BuildBotWorkerSg', { + vpc: props.vpc, + description: 'SG for worker nodes to use.', + }); + this.workerSecurityGroup.addEgressRule(Peer.anyIpv4(), Port.allTraffic(), 'Allow All Outbound'); + this.workerSecurityGroup.addIngressRule( + Peer.ipv4(props.vpc.vpcCidrBlock), + Port.allTcp(), + 'Allow All TCP inside the VPC', + ); + + this.securityGroups = [this.workerSecurityGroup]; + + if (props.useWebhookAPI) { + this.securityGroups.push(this.addWebhookAPI(props.vpc)); + } + + // Set up the ECS Server. + const buildBotServerTask = new ecs.TaskDefinition(this, 'BuildBotServerTask', { + compatibility: ecs.Compatibility.FARGATE, + cpu: '1024', + memoryMiB: '2048', + volumes: [ + { + name: 'DBMount', + efsVolumeConfiguration: { + fileSystemId: props.filesystem.fileSystemId, + transitEncryption: 'ENABLED', + authorizationConfig: { + accessPointId: props.accesspoint.accessPointId, + iam: 'ENABLED', + }, + }, + }, + ], + }); + const serverContainer = buildBotServerTask.addContainer('ServerContainer', { + image: ecs.ContainerImage.fromRegistry(props.imageRepository.repositoryUri), + containerName: 'buildbot-server', + portMappings: [ + { containerPort: 8010, protocol: ecs.Protocol.TCP }, + { containerPort: 9989, protocol: ecs.Protocol.TCP }, + ], + logging: new ecs.AwsLogDriver({ streamPrefix: 'buildbot' }), + }); + serverContainer.addMountPoints({ + containerPath: '/mount/data', + sourceVolume: 'DBMount', + readOnly: false, + }); + + const buildBotService = new ecs.FargateService(this, 'BuildBotService', { + desiredCount: 1, + minHealthyPercent: 0, + maxHealthyPercent: 100, + taskDefinition: buildBotServerTask, + cluster: props.cluster, + serviceName: props.serviceName, + securityGroups: this.securityGroups, + enableExecuteCommand: true, + cloudMapOptions: { + cloudMapNamespace: props.namespace, + name: 'buildbot', + }, + healthCheckGracePeriod: Duration.seconds(150), + }); + + // TODO: Find equivalent for: + // Peer.ipv4(props.vpc.vpcCidrBlock), + buildBotService.connections.allowFromAnyIpv4(Port.tcp(9989)); + buildBotService.connections.allowTo(props.filesystem, Port.tcp(2049)); + + // Create Internet Facing Load Balancer. + const webLb = new ApplicationLoadBalancer(this, 'WebLB', { + vpc: props.vpc, + internetFacing: true, + securityGroup: buildbotSg, + }); + + const loadBalancerAccessLogs = new Bucket(this, 'LoadBalancerLogs', {}); + webLb.logAccessLogs(loadBalancerAccessLogs); + + let webListener: ApplicationListener; + if (props.dns) { + webListener = webLb.addListener('Listener', { + protocol: ApplicationProtocol.HTTPS, + certificates: [props.dns.cert], + open: true, + }); + } else { + webListener = webLb.addListener('Listener', { + protocol: ApplicationProtocol.HTTP, + open: true, + }); + } + + const webTargetGroup = webListener.addTargets('WebTarget', { + port: 8010, + targets: [buildBotService], + protocol: ApplicationProtocol.HTTP, + }); + + webTargetGroup.configureHealthCheck({ + path: '/api/v2', + interval: Duration.seconds(150), + }); + + // Allow our service to create and manage EC2 Workers. + buildBotService.taskDefinition.taskRole.addManagedPolicy( + ManagedPolicy.fromManagedPolicyArn( + this, + 'BuildBotServerPolicy', + 'arn:aws:iam::aws:policy/AmazonEC2FullAccess', + ), + ); + + if (props.dns) { + new route53.ARecord(this, 'ApigwARecord', { + target: route53.RecordTarget.fromAlias(new LoadBalancerTarget(webLb)), + zone: props.dns.zone, + }); + + if (props.oidcAction) { + webListener.addAction('SSO', { + action: ListenerAction.authenticateOidc({ + ...props.oidcAction, + next: ListenerAction.forward([webTargetGroup]), + }), + }); + } + } + + props.imageRepository.grantPull(buildBotService.taskDefinition.obtainExecutionRole()); + this.service = buildBotService; + + // This allows for 'Session Manager' based connections to the worker instances for debug. + const workerRole = new Role(this, 'EC2WorkerRole', { + assumedBy: new ServicePrincipal('ec2.amazonaws.com'), + managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore')], + }); + + // Needed to attach the appropriate IAM Role to the Instance Profile from the buildbot server. + buildBotServerTask.addToTaskRolePolicy( + new PolicyStatement({ + effect: Effect.ALLOW, + actions: [ + 'iam:PassRole', + 'codecommit:BatchGet*', + 'codecommit:Describe*', + 'codecommit:Get*', + 'codecommit:GitPull', + 'codecommit:List*', + ], + // TODO resources: [workerRole.roleArn], + resources: ['*'], + }), + ); + + // This Instance Profile will carry the Worker Role to the actual worker instance. + const instanceProfileName = 'buildbot-worker-profile'; + new CfnInstanceProfile(this, 'EC2WorkerProfile', { + roles: [workerRole.roleName], + instanceProfileName: instanceProfileName, + }); + + new CfnOutput(this, 'WorkerInstanceProfile', { + value: instanceProfileName, + }); + + if (props.dns) { + this.loadBalancerDNS = 'https://' + props.dns.zone.zoneName; + } else { + this.loadBalancerDNS = 'http://' + webLb.loadBalancerDnsName; + } + // loadBalancerDNS.toLowerCase(); does not work!? -> gives that??? http://${token[token.1744]} + + new CfnOutput(this, 'LoadBalancerDNS', { value: this.loadBalancerDNS }); + new CfnOutput(this, 'WorkerSecurityGroup', { + value: this.workerSecurityGroup.securityGroupId, + }); + + this.workerVpcSubnetID = props.vpc.privateSubnets[0].subnetId; + + // sstate-volume + const fileSystem = new FileSystem(this, 'Efs', { + vpc: props.vpc, + securityGroup: this.workerSecurityGroup, + // performanceMode: PerformanceMode.GENERAL_PURPOSE, + // vpcSubnets: { + // subnetType: SubnetType.PRIVATE_WITH_EGRESS, + // onePerAz: true, + // availabilityZones: [props.vpc.privateSubnets[0].availabilityZone], + // }, + }); + + buildBotServerTask.addToTaskRolePolicy( + new PolicyStatement({ + actions: [ + 'elasticfilesystem:ClientRootAccess', + 'elasticfilesystem:ClientWrite', + 'elasticfilesystem:ClientMount', + 'elasticfilesystem:DescribeMountTargets', + ], + resources: [ + `arn:aws:elasticfilesystem:${this.region}:${this.account}:file-system/${fileSystem.fileSystemId}`, + props.filesystem.fileSystemArn, + ], + }), + ); + + buildBotServerTask.addToTaskRolePolicy( + new PolicyStatement({ + actions: ['ec2:DescribeAvailabilityZones'], + resources: ['*'], + }), + ); + + this.sstateFS = fileSystem; + } + + private addWebhookAPI(vpc: IVpc): ISecurityGroup { + const webhookApi = new WebhookAPI(this, 'WebhookAPI', { + vpc, + }); + + return webhookApi.securitygroup; + } +} diff --git a/lib/buildbot/index.ts b/lib/buildbot/index.ts new file mode 100644 index 0000000..18c613c --- /dev/null +++ b/lib/buildbot/index.ts @@ -0,0 +1,5 @@ +export * from './buildbot-pipeline'; +export * from './buildbot-server'; +export * from './buildbot-config'; +export * from './buildbot-image-repo'; +export * from './buildbot-data'; diff --git a/lib/buildbot/lambda-source/index.py b/lib/buildbot/lambda-source/index.py new file mode 100644 index 0000000..62e723e --- /dev/null +++ b/lib/buildbot/lambda-source/index.py @@ -0,0 +1,37 @@ +import json, urllib.request, urllib.error + +def strip_in_headers(headers): + return headers + +def lambda_handler(event, context): + print(json.dumps(event)) + + url = "http://buildbot.service:8010/change_hook/github" + + try: + data = event['body'].encode() + + print("construct http request") + req = urllib.request.Request(url, data=data) + if 'headers' in event and isinstance(event['headers'], dict) and len(event['headers']) > 0: + for incoming_header in strip_in_headers(event['headers']): + req.add_header(incoming_header, event['headers'][incoming_header]) + + buildbot_response = urllib.request.urlopen(req) + + print("buildbot_response:" + buildbot_response.read().decode()) + buildbot_response = { + "statusCode": buildbot_response.status, + "body": "OK", + } + + except urllib.error.HTTPError as e: + buildbot_response = { + "statusCode": 400, + "body": "ERROR", + } + print("exception handler:") + print(e) + + print("sending response to GitHub") + return buildbot_response diff --git a/lib/cache-server/cache-ecr.ts b/lib/cache-server/cache-ecr.ts new file mode 100644 index 0000000..aaf8164 --- /dev/null +++ b/lib/cache-server/cache-ecr.ts @@ -0,0 +1,16 @@ +import * as cdk from 'aws-cdk-lib'; +import * as ecr from 'aws-cdk-lib/aws-ecr'; + +export interface CacheEcrProps extends cdk.StackProps { + // optional name of the ecr registry. + readonly repositoryName?: string; +} + +export class CacheEcrStack extends cdk.Stack { + public readonly repo: ecr.Repository; + + constructor(scope: cdk.App, id: string, props: CacheEcrProps) { + super(scope, id, { ...props }); + this.repo = new ecr.Repository(this, props.repositoryName || 'cacheRegistry', {}); + } +} diff --git a/lib/cache-server/cache-ecs.ts b/lib/cache-server/cache-ecs.ts new file mode 100644 index 0000000..212fabc --- /dev/null +++ b/lib/cache-server/cache-ecs.ts @@ -0,0 +1,108 @@ +import * as cdk from 'aws-cdk-lib'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as ecr from 'aws-cdk-lib/aws-ecr'; +import { INamespace } from 'aws-cdk-lib/aws-servicediscovery'; +import { AwsLogDriver } from 'aws-cdk-lib/aws-ecs'; +import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs'; + +export interface CacheEcsProps extends cdk.StackProps { + // optional name of the fargate service. + readonly serviceName?: string; + + readonly repository: ecr.IRepository; + + readonly vpc: ec2.Vpc; + readonly cluster: ecs.ICluster; + readonly namespace: INamespace; +} + +export class CacheEcsStack extends cdk.Stack { + public readonly service: ecs.IBaseService; + + constructor(scope: cdk.App, id: string, props: CacheEcsProps) { + super(scope, id, { ...props }); + + const cacheLogGroup = new LogGroup(this, 'CACHELogGroup', { + logGroupName: '/ecs/cache', + removalPolicy: cdk.RemovalPolicy.DESTROY, + retention: RetentionDays.ONE_YEAR, + }); + + const cacheTaskDefinition = new ecs.FargateTaskDefinition(this, 'CACHETask', { + cpu: 2048, + memoryLimitMiB: 16384, + ephemeralStorageGiB: 200, + }); + + cacheTaskDefinition.addContainer('AppContainer', { + containerName: 'cache-server', + image: ecs.ContainerImage.fromRegistry(props.repository.repositoryUri), + logging: new AwsLogDriver({ + logGroup: cacheLogGroup, + streamPrefix: 'CACHE-Cache', + }), + portMappings: [{ containerPort: 80 }, { containerPort: 873 }, { containerPort: 8687 }], + }); + + const patterns = new ecs_patterns.NetworkMultipleTargetGroupsFargateService(this, 'CACHEService', { + cluster: props.cluster, + taskDefinition: cacheTaskDefinition, + loadBalancers: [ + { + name: 'cache-loadbalancer', + listeners: [ + { name: 'cache-listener', port: 80 }, + { name: 'rsync-listener', port: 873 }, + { name: 'hashequiv-listener', port: 8687 }, + ], + publicLoadBalancer: false, + }, + ], + targetGroups: [ + { + containerPort: 80, + listener: 'cache-listener', + }, + { + containerPort: 873, + listener: 'rsync-listener', + }, + { + containerPort: 8687, + listener: 'hashequiv-listener', + }, + ], + serviceName: props.serviceName ?? 'cache', + memoryLimitMiB: 16384, // Default is 512 + cpu: 2048, // Default is 256 + cloudMapOptions: { + cloudMapNamespace: props.namespace, + name: 'cache', + }, + }); + + const ports = new Map([ + ['HTTP', 80], + ['RSYNC', 873], + ['HASHEQUIV', 8687], + ]); + + for (const [_, port] of ports) { + const p = new ec2.Port({ + protocol: ec2.Protocol.TCP, + stringRepresentation: port.toString(), + fromPort: port, + toPort: port, + }); + patterns.service.connections.allowFromAnyIpv4(p); + } + + // service.registerLoadBalancer('loadbalancer', patterns.loadBalancer); + + props.repository.grantPull(patterns.taskDefinition.obtainExecutionRole()); + + this.service = patterns.service; + } +} diff --git a/lib/cache-server/cache-pipeline.ts b/lib/cache-server/cache-pipeline.ts new file mode 100644 index 0000000..64ab4ae --- /dev/null +++ b/lib/cache-server/cache-pipeline.ts @@ -0,0 +1,91 @@ +import * as cdk from 'aws-cdk-lib'; +import * as codepipeline from 'aws-cdk-lib/aws-codepipeline'; +import * as codebuild from 'aws-cdk-lib/aws-codebuild'; +import * as codepipeline_actions from 'aws-cdk-lib/aws-codepipeline-actions'; +import { Repository } from 'aws-cdk-lib/aws-codecommit'; +import { BuildSpec, ComputeType, LinuxBuildImage, PipelineProject } from 'aws-cdk-lib/aws-codebuild'; +import * as ecr from 'aws-cdk-lib/aws-ecr'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; + +export interface CachePipelineProps extends cdk.StackProps { + // + readonly cacheRepo: string; + + readonly repository: ecr.IRepository; + + readonly cacheRepoBranch?: string; + + // X2 Large Compute is not available in new accounts. + readonly enableXLCompute?: boolean; + + // ecs to trigger restart after build + readonly ecsCacheFargateService: ecs.IBaseService; +} + +export class CachePipeline extends cdk.Stack { + constructor(scope: cdk.App, id: string, props: CachePipelineProps) { + super(scope, id, { ...props }); + + const compute = props.enableXLCompute ? ComputeType.X2_LARGE : ComputeType.LARGE; + + const cacheSourceOutput = new codepipeline.Artifact('CacheSourceArtifact'); + const cacheSourceAction = new codepipeline_actions.CodeCommitSourceAction({ + actionName: 'Source', + repository: Repository.fromRepositoryName(this, 'Repo', props.cacheRepo), + output: cacheSourceOutput, + branch: props.cacheRepoBranch ?? 'mainline', + }); + + const buildRunPipeline = new PipelineProject(this, 'BuildCacheDocker', { + buildSpec: BuildSpec.fromSourceFilename('buildspec.yml'), + environment: { + computeType: compute, + buildImage: LinuxBuildImage.STANDARD_5_0, + // privileged = true is needed in order to run docker build: + privileged: true, + }, + environmentVariables: { + // It is expected that our buildspec.yml in our source code will reference + // this environment variable to determine which ECR repo to push the built image. + ECR_REPOSITORY_URI: { + type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, + value: props.repository.repositoryUri, + }, + }, + timeout: cdk.Duration.hours(8), + }); + props.repository.grantPullPush(buildRunPipeline); + + const buildOutput = new codepipeline.Artifact('CACHEImageBuildOutput'); + const buildRunAction = new codepipeline_actions.CodeBuildAction({ + actionName: 'Build', + project: buildRunPipeline, + input: cacheSourceOutput, + outputs: [buildOutput], + }); + + const deployAction = new codepipeline_actions.EcsDeployAction({ + actionName: 'Deploy', + service: props.ecsCacheFargateService, + input: buildOutput, + }); + + new codepipeline.Pipeline(this, 'CacheDockerGenPipeline', { + pipelineName: 'CacheDockerGenPipeline', + stages: [ + { + stageName: 'Source', + actions: [cacheSourceAction], + }, + { + stageName: 'Build', + actions: [buildRunAction], + }, + { + stageName: 'Deploy', + actions: [deployAction], + }, + ], + }); + } +} diff --git a/lib/cache-server/cache-repo.ts b/lib/cache-server/cache-repo.ts new file mode 100644 index 0000000..f4898bd --- /dev/null +++ b/lib/cache-server/cache-repo.ts @@ -0,0 +1,21 @@ +import * as cdk from 'aws-cdk-lib'; +import * as codecommit from 'aws-cdk-lib/aws-codecommit'; + +export interface CacheRepoProps extends cdk.StackProps { + // optional name of the ecr registry. + readonly repositoryName?: string; +} + +export class CacheRepoStack extends cdk.Stack { + public readonly repo: codecommit.IRepository; + + constructor(scope: cdk.App, id: string, props: CacheRepoProps) { + super(scope, id, { ...props }); + + // Add a codecommit repo to be a read replica of our internal cache repo. + this.repo = new codecommit.Repository(this, 'CacheRepo', { + repositoryName: props.repositoryName || 'CacheScripts', + description: 'Scripts to build cache docker container.', + }); + } +} diff --git a/lib/cache-server/index.ts b/lib/cache-server/index.ts new file mode 100644 index 0000000..1bd4d04 --- /dev/null +++ b/lib/cache-server/index.ts @@ -0,0 +1,4 @@ +export * from './cache-repo'; +export * from './cache-pipeline'; +export * from './cache-ecr'; +export * from './cache-ecs'; diff --git a/lib/constants.ts b/lib/constants.ts new file mode 100644 index 0000000..a66b8f2 --- /dev/null +++ b/lib/constants.ts @@ -0,0 +1,54 @@ +import { OidcAction } from './buildbot'; +import { SecretValue } from 'aws-cdk-lib'; + +interface DeveloperDNS { + readonly superNovaZoneName: string; + readonly superNovaZoneId: string; + readonly dnsAccountId: string; +} + +interface StageProps { + readonly name: string; + readonly accountId: string; + readonly region: string; + readonly org: string; + readonly developerDns?: DeveloperDNS; +} + +interface DevStages { + [name: string]: StageProps; +} + +export const DeveloperStages: DevStages = { + glimsdal: { + name: 'glimsdal', + accountId: '649821364639', + region: 'us-west-2', + org: 'glimsdal', + developerDns: { + superNovaZoneId: 'Z05913182COOR61Q0UZRO', + superNovaZoneName: 'glimsdal.people.aws.dev', + dnsAccountId: '274048645864', + }, + }, + throos: { + name: 'throos', + accountId: '743600277648', + region: 'us-west-2', + org: 'throos', + developerDns: { + superNovaZoneId: 'Z08427691VZ4BX9HVYBXB', + superNovaZoneName: 'throos.people.aws.dev', + dnsAccountId: '743600277648', + }, + }, +}; + +export const FederateOIDC: OidcAction = { + issuer: 'https://idp-integ.federate.amazon.com', + tokenEndpoint: 'https://idp-integ.federate.amazon.com/api/oauth2/v2/token', + userInfoEndpoint: 'https://idp-integ.federate.amazon.com/api/oauth2/v1/userinfo', + authorizationEndpoint: 'https://idp-integ.federate.amazon.com/api/oauth2/v1/authorize', + clientId: 'buildbot-glimsdal-dev', + clientSecret: SecretValue.secretsManager('oidc-token', {}), +}; diff --git a/lib/constructs/bucket.ts b/lib/constructs/bucket.ts new file mode 100644 index 0000000..7eba81c --- /dev/null +++ b/lib/constructs/bucket.ts @@ -0,0 +1,39 @@ +import * as cdk from 'aws-cdk-lib'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import { Construct } from 'constructs'; + +export interface BucketProps extends cdk.StackProps { + /** + * The name of the inner S3 Bucket. + * + * @default A Cloudformation auto-generated name. + */ + readonly bucketName?: string; +} + +/** + * An S3 Bucket with some secure default options set. + */ +export class Bucket extends Construct { + /** + * The inner S3 bucket. + */ + readonly bucket: s3.IBucket; + + constructor(scope: Construct, id: string, props: BucketProps) { + super(scope, id); + const accessLogBucket = new s3.Bucket(this, 'AccessLogBucket', {}); + + // TODO(glimsdal): Setup ACL for VPC endpoints: https://docs.aws.amazon.com/vpc/latest/privatelink/vpc-endpoints-s3.html + this.bucket = new s3.Bucket(this, 'Bucket', { + // Block all public access. Public Access is not required for this application. + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + // Note that this is not reversible without deleting the bucket. + versioned: true, + // Prevent regular HTTP traffic. + enforceSSL: true, + serverAccessLogsBucket: accessLogBucket, + bucketName: props.bucketName, + }); + } +} diff --git a/lib/constructs/index.ts b/lib/constructs/index.ts new file mode 100644 index 0000000..936f938 --- /dev/null +++ b/lib/constructs/index.ts @@ -0,0 +1,2 @@ +export * from './webhook-api'; +export * from './bucket'; diff --git a/lib/constructs/webhook-api.ts b/lib/constructs/webhook-api.ts new file mode 100644 index 0000000..dbcff2c --- /dev/null +++ b/lib/constructs/webhook-api.ts @@ -0,0 +1,117 @@ +import * as cdk from 'aws-cdk-lib'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigw from 'aws-cdk-lib/aws-apigateway'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as path from 'path'; +import * as fs from 'fs'; +import { Duration } from 'aws-cdk-lib'; +import { Construct } from 'constructs'; + +export interface WebhookAPIProps extends cdk.StackProps { + /** + * The name of the inner lambda function. Specifying the name can be useful + * for discovering the function during testing. + * + * @default A Cloudformation auto-generated name. + */ + readonly functionName?: string; + /** + * The VPC to put the lambda function's network interface in. This should + * be the same VPC as the buildbot server. + */ + readonly vpc: ec2.IVpc; +} + +/** + * + */ +export class WebhookAPI extends Construct { + /** + * The inner lambda function. + */ + readonly function: lambda.IFunction; + /** + * A security group for the lambda function's network interface. + */ + readonly securitygroup: ec2.ISecurityGroup; + + constructor(scope: Construct, id: string, props: WebhookAPIProps) { + super(scope, id); + + this.securitygroup = new ec2.SecurityGroup(this, 'WebhookSecurityGroup', { + vpc: props.vpc, + description: 'Security Group for Webhook Intercepting Lambda.', + }); + + // Function to check if the event initiator is authorized. + this.function = new lambda.Function(this, 'WebhookFunction', { + functionName: props.functionName, + code: lambda.Code.fromInline( + fs.readFileSync(path.join(__dirname, '../../../lib/buildbot/lambda-source', 'index.py'), { + encoding: 'utf-8', + }), + ), + handler: 'index.lambda_handler', + timeout: Duration.minutes(1), + runtime: lambda.Runtime.PYTHON_3_8, + memorySize: 128, + vpc: props.vpc, + description: 'this will receive json payload by this webhook to trigger buildbot builds', + securityGroups: [this.securitygroup], + }); + + this.function.addToRolePolicy( + // TODO(glimsdal): Explicitly set resources here. + new iam.PolicyStatement({ + actions: ['secretsmanager:GetSecretValue'], + resources: ['*'], + }), + ); + + // This Policy should block any non-github IP from reaching our webhook endpoint. + const apiPolicy = iam.PolicyDocument.fromJson({ + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Principal: '*', + Action: 'execute-api:Invoke', + Resource: ['execute-api:/*'], + }, + { + Effect: 'Deny', + Principal: '*', + Action: 'execute-api:Invoke', + Resource: ['execute-api:/*'], + Condition: { + NotIpAddress: { + 'aws:SourceIp': [ + // This can be found at https://api.github.com/meta under 'hooks'. + '192.30.252.0/22', + '185.199.108.0/22', + '140.82.112.0/20', + '143.55.64.0/20', + ], + }, + }, + }, + ], + }); + + const apigwLogs = new logs.LogGroup(this, 'loggroup', { + retention: logs.RetentionDays.TEN_YEARS, + removalPolicy: cdk.RemovalPolicy.RETAIN, + }); + + new apigw.LambdaRestApi(this, 'restapi', { + handler: this.function, + policy: apiPolicy, + deployOptions: { + accessLogDestination: new apigw.LogGroupLogDestination(apigwLogs), + accessLogFormat: apigw.AccessLogFormat.jsonWithStandardFields(), + }, + }); + } +} diff --git a/lib/stacks/cluster.ts b/lib/stacks/cluster.ts new file mode 100644 index 0000000..3ca1a5a --- /dev/null +++ b/lib/stacks/cluster.ts @@ -0,0 +1,20 @@ +import { App, Stack, StackProps } from 'aws-cdk-lib'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; + +export interface ClusterProps extends StackProps { + readonly vpc: ec2.IVpc; +} + +export class Cluster extends Stack { + public readonly cluster: ecs.ICluster; + + constructor(scope: App, id: string, props: ClusterProps) { + super(scope, id, props); + + this.cluster = new ecs.Cluster(this, 'Cluster', { + vpc: props.vpc, + containerInsights: true, + }); + } +} diff --git a/lib/stacks/index.ts b/lib/stacks/index.ts new file mode 100644 index 0000000..e404eb3 --- /dev/null +++ b/lib/stacks/index.ts @@ -0,0 +1,3 @@ +export * from './vpc'; +export * from './cluster'; +export * from './servicediscovery'; diff --git a/lib/stacks/servicediscovery.ts b/lib/stacks/servicediscovery.ts new file mode 100644 index 0000000..531928d --- /dev/null +++ b/lib/stacks/servicediscovery.ts @@ -0,0 +1,23 @@ +import { App, Stack, StackProps } from 'aws-cdk-lib'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as servicediscovery from 'aws-cdk-lib/aws-servicediscovery'; + +export interface ServiceDomainProps extends StackProps { + readonly vpc: ec2.IVpc; +} + +/** + * A Vpc Stack with Flowlogs and endpoints enabled. + */ +export class ServiceDomain extends Stack { + public readonly namespace: servicediscovery.IPrivateDnsNamespace; + + constructor(scope: App, id: string, props: ServiceDomainProps) { + super(scope, id, { ...props }); + this.namespace = new servicediscovery.PrivateDnsNamespace(this, 'ServiceNamespace', { + vpc: props.vpc, + description: 'DNS Namespace for internal VPC discovery.', + name: 'service', + }); + } +} diff --git a/lib/stacks/vpc.ts b/lib/stacks/vpc.ts new file mode 100644 index 0000000..01ea25a --- /dev/null +++ b/lib/stacks/vpc.ts @@ -0,0 +1,34 @@ +import { App, Stack, StackProps } from 'aws-cdk-lib'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs'; + +export type VpcProps = StackProps; + +/** + * A Vpc Stack with Flowlogs and endpoints enabled. + */ +export class Vpc extends Stack { + public readonly vpc: ec2.Vpc; + + constructor(scope: App, id: string, props: VpcProps) { + super(scope, id, { ...props }); + + this.vpc = new ec2.Vpc(this, 'Vpc', { + ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'), + enableDnsHostnames: true, + enableDnsSupport: true, + defaultInstanceTenancy: ec2.DefaultInstanceTenancy.DEFAULT, + }); + + // TODO: Add Endpoints. Something was leaking from private Subnets. + + new ec2.FlowLog(this, 'VPCFlowLogs', { + resourceType: ec2.FlowLogResourceType.fromVpc(this.vpc), + destination: ec2.FlowLogDestination.toCloudWatchLogs( + new LogGroup(this, 'LogGroup', { + retention: RetentionDays.ONE_YEAR, + }), + ), + }); + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..28f8133 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2109 @@ +{ + "name": "aws4embeddedlinux-buildbot", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "aws4embeddedlinux-buildbot", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "optionator": "^0.9.1" + }, + "devDependencies": { + "@types/node": "^20.2.3", + "@typescript-eslint/eslint-plugin": "^5.59.7", + "@typescript-eslint/parser": "^5.59.7", + "@typescript-eslint/typescript-estree": "^5.59.7", + "aws-cdk": "^2.80.0", + "aws-cdk-lib": "^2.80.0", + "constructs": "^10.2.31", + "eslint": "^8.41.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-prettier": "^4.2.1", + "prettier": "^2.8.8", + "typescript": "^5.0.4" + } + }, + "node_modules/@aws-cdk/asset-awscli-v1": { + "version": "2.2.183", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.183.tgz", + "integrity": "sha512-D+E0drcgCjDn43GjsATIpkMeZlFoQFmf86GSlIF0AtNyl0IVrNKsKs/7J4oESrCTTCLL/x9vS7mG9e1ZrKbLpw==", + "dev": true + }, + "node_modules/@aws-cdk/asset-kubectl-v20": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.1.tgz", + "integrity": "sha512-U1ntiX8XiMRRRH5J1IdC+1t5CE89015cwyt5U63Cpk0GnMlN5+h9WsWMlKlPXZR4rdq/m806JRlBMRpBUB2Dhw==", + "dev": true + }, + "node_modules/@aws-cdk/asset-node-proxy-agent-v5": { + "version": "2.0.153", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v5/-/asset-node-proxy-agent-v5-2.0.153.tgz", + "integrity": "sha512-EL2XCW6C3szBPHI4XWQsb68pqc719nR+8sfsEhyjzBNn0idgZg2ViujI3/1/jWnJwHPnvMOKQcmr/bOKzCPAgA==", + "dev": true + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.2", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.41.0.tgz", + "integrity": "sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.2.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.3.tgz", + "integrity": "sha512-pg9d0yC4rVNWQzX8U7xb4olIOFuuVL9za3bzMT2pu2SU0SNEi66i2qrvhE2qt0HvkhuCaWJu7pLNOt/Pj8BIrw==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.59.7", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.7.tgz", + "integrity": "sha512-BL+jYxUFIbuYwy+4fF86k5vdT9lT0CNJ6HtwrIvGh0PhH8s0yy5rjaKH2fDCrz5ITHy07WCzVGNvAmjJh4IJFA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.59.7", + "@typescript-eslint/type-utils": "5.59.7", + "@typescript-eslint/utils": "5.59.7", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.59.7", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.7.tgz", + "integrity": "sha512-VhpsIEuq/8i5SF+mPg9jSdIwgMBBp0z9XqjiEay+81PYLJuroN+ET1hM5IhkiYMJd9MkTz8iJLt7aaGAgzWUbQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.59.7", + "@typescript-eslint/types": "5.59.7", + "@typescript-eslint/typescript-estree": "5.59.7", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.59.7", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.7.tgz", + "integrity": "sha512-FL6hkYWK9zBGdxT2wWEd2W8ocXMu3K94i3gvMrjXpx+koFYdYV7KprKfirpgY34vTGzEPPuKoERpP8kD5h7vZQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.7", + "@typescript-eslint/visitor-keys": "5.59.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.59.7", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.7.tgz", + "integrity": "sha512-ozuz/GILuYG7osdY5O5yg0QxXUAEoI4Go3Do5xeu+ERH9PorHBPSdvD3Tjp2NN2bNLh1NJQSsQu2TPu/Ly+HaQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.59.7", + "@typescript-eslint/utils": "5.59.7", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.59.7", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.7.tgz", + "integrity": "sha512-UnVS2MRRg6p7xOSATscWkKjlf/NDKuqo5TdbWck6rIRZbmKpVNTLALzNvcjIfHBE7736kZOFc/4Z3VcZwuOM/A==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.59.7", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.7.tgz", + "integrity": "sha512-4A1NtZ1I3wMN2UGDkU9HMBL+TIQfbrh4uS0WDMMpf3xMRursDbqEf1ahh6vAAe3mObt8k3ZATnezwG4pdtWuUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.7", + "@typescript-eslint/visitor-keys": "5.59.7", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.59.7", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.7.tgz", + "integrity": "sha512-yCX9WpdQKaLufz5luG4aJbOpdXf/fjwGMcLFXZVPUz3QqLirG5QcwwnIHNf8cjLjxK4qtzTO8udUtMQSAToQnQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.59.7", + "@typescript-eslint/types": "5.59.7", + "@typescript-eslint/typescript-estree": "5.59.7", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.59.7", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.7.tgz", + "integrity": "sha512-tyN+X2jvMslUszIiYbF0ZleP+RqQsFVpGrKI6e0Eet1w8WmhsAtmzaqm8oM8WJQ1ysLwhnsK/4hYHJjOgJVfQQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.7", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk": { + "version": "2.80.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.80.0.tgz", + "integrity": "sha512-SKMZ/sGlNmFV37Lk40HHe4QJ2hJZmD0PrkScBmkr33xzEqjyKhN3jIHC4PYqTUeUK/qYemq3Y5OpXKQuWTCoKA==", + "dev": true, + "bin": { + "cdk": "bin/cdk" + }, + "engines": { + "node": ">= 14.15.0" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/aws-cdk-lib": { + "version": "2.80.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.80.0.tgz", + "integrity": "sha512-PoqD3Yms5I0ajuTi071nTW/hpkH3XsdyZzn5gYsPv0qD7mqP3h6Qr+6RiGx+yQ1KcVFyxWdX15uK+DsC0KwvcQ==", + "bundleDependencies": [ + "@balena/dockerignore", + "case", + "fs-extra", + "ignore", + "jsonschema", + "minimatch", + "punycode", + "semver", + "table", + "yaml" + ], + "dev": true, + "dependencies": { + "@aws-cdk/asset-awscli-v1": "^2.2.177", + "@aws-cdk/asset-kubectl-v20": "^2.1.1", + "@aws-cdk/asset-node-proxy-agent-v5": "^2.0.148", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.1.1", + "ignore": "^5.2.4", + "jsonschema": "^1.4.1", + "minimatch": "^3.1.2", + "punycode": "^2.3.0", + "semver": "^7.5.1", + "table": "^6.8.1", + "yaml": "1.10.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "constructs": "^10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/aws-cdk-lib/node_modules/ajv": { + "version": "8.12.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/astral-regex": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/aws-cdk-lib/node_modules/case": { + "version": "1.6.3", + "dev": true, + "inBundle": true, + "license": "(MIT OR GPL-3.0-or-later)", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fs-extra": { + "version": "11.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/aws-cdk-lib/node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/ignore": { + "version": "5.2.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/jsonfile": { + "version": "6.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/jsonschema": { + "version": "1.4.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { + "version": "4.4.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/punycode": { + "version": "2.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/aws-cdk-lib/node_modules/require-from-string": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/semver": { + "version": "7.5.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/slice-ansi": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/table": { + "version": "6.8.1", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/universalify": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/yaml": { + "version": "1.10.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/constructs": { + "version": "10.2.31", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.2.31.tgz", + "integrity": "sha512-r49ORPNWmPXAVWADpqKjhL+Pa9m9AgaSrUfI4U7w7whhY4Eg2+2E4TSAdssam05WCFl59HiTd2aW5DikrNyycA==", + "dev": true, + "engines": { + "node": ">= 16.14.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.41.0.tgz", + "integrity": "sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.41.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", + "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/espree": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9c97b80 --- /dev/null +++ b/package.json @@ -0,0 +1,36 @@ +{ + "name": "aws4embeddedlinux-buildbot", + "version": "0.1.0", + "license": "MIT", + "main": "dist/lib/app.js", + "types": "dist/types/app.d.ts", + "scripts": { + "zip-config": "if [ -f dist/admin-config/config.zip ]; then rm -rf dist/admin-config; fi && mkdir -p dist/admin-config ; cd configuration/admin/ && zip -q -o ../../dist/admin-config/config.zip -r *", + "clean": "rm -rf ./js/ && rm -rf dist && rm -rf cdk.out && rm -rf dist/admin-config", + "build": "tsc", + "watch": "tsc -w", + "prepare": "npm run zip-config && npm run-script build", + "lint": "eslint '*/**/*.{js,ts,tsx}' --quiet --fix", + "pretest": "eslint '*/**/*.{js,ts,tsx}'", + "test": "echo OK", + "format": "eslint '**/*.{js,ts,json}' --quiet --fix", + "check": "eslint '**/*.{js,ts,json}'" + }, + "devDependencies": { + "@types/node": "^20.2.3", + "@typescript-eslint/eslint-plugin": "^5.59.7", + "@typescript-eslint/parser": "^5.59.7", + "@typescript-eslint/typescript-estree": "^5.59.7", + "aws-cdk": "^2.80.0", + "aws-cdk-lib": "^2.80.0", + "constructs": "^10.2.31", + "eslint": "^8.41.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-prettier": "^4.2.1", + "prettier": "^2.8.8", + "typescript": "^5.0.4" + }, + "dependencies": { + "optionator": "^0.9.1" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2fb432e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2018", + "module": "commonjs", + "lib": ["es2016", "es2017.object", "es2017.string"], + "declaration": true, + "outDir": "./dist/lib", + "declarationDir": "./dist/types", + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": ["./node_modules/@types"] + }, + "exclude": ["cdk.out", "build", "node_modules", "dist"] +}