moving everything to active or retired vs incubating and graduated
All checks were successful
Reese's Arch Toolbox / build-and-push-arch-toolbox (push) Successful in 14s
All checks were successful
Reese's Arch Toolbox / build-and-push-arch-toolbox (push) Successful in 14s
This commit is contained in:
1
active/podman_ddns/.python-version
Normal file
1
active/podman_ddns/.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.13
|
||||
7
active/podman_ddns/.vscode/settings.json
vendored
Normal file
7
active/podman_ddns/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"python.testing.pytestArgs": [
|
||||
"."
|
||||
],
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestEnabled": true
|
||||
}
|
||||
23
active/podman_ddns/Containerfile
Normal file
23
active/podman_ddns/Containerfile
Normal file
@@ -0,0 +1,23 @@
|
||||
FROM python:3.12-slim-bookworm
|
||||
|
||||
# The installer requires curl (and certificates) to download the release archive
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates
|
||||
|
||||
# Download the latest installer
|
||||
ADD https://astral.sh/uv/install.sh /uv-installer.sh
|
||||
|
||||
# Run the installer then remove it
|
||||
RUN sh /uv-installer.sh && rm /uv-installer.sh
|
||||
|
||||
# Ensure the installed binary is on the `PATH`
|
||||
ENV PATH="/root/.local/bin/:$PATH"
|
||||
|
||||
# Copy the project into the image
|
||||
ADD update.py uv.lock pyproject.toml /app
|
||||
|
||||
# Sync the project into a new environment, using the frozen lockfile
|
||||
WORKDIR /app
|
||||
RUN uv sync --frozen
|
||||
|
||||
# Presuming there is a `my_app` command provided by the project
|
||||
CMD ["uv", "run", "update.py"]
|
||||
21
active/podman_ddns/ddns.container
Normal file
21
active/podman_ddns/ddns.container
Normal file
@@ -0,0 +1,21 @@
|
||||
[Unit]
|
||||
Description=DDNS
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Container]
|
||||
Environment=ROUTE53_RECORD={{ item.record }}
|
||||
Environment=HOSTED_ZONE_ID={{ item.hosted_zone_id }}
|
||||
Environment=AWS_ACCESS_KEY_ID={{ aws.access_key_id }}
|
||||
Environment=AWS_SECRET_ACCESS_KEY={{ aws.secret_access_key }}
|
||||
{% if item.skip_ipv6 | default(false) %}
|
||||
Environment=SKIP_IPV6=true
|
||||
{% endif %}
|
||||
{% if item.skip_ipv4 | default(false) %}
|
||||
Environment=SKIP_IPV4=true
|
||||
{% endif %}
|
||||
Image=gitea.reeseapps.com/services/ddns:latest
|
||||
Network=ddns.network
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
130
active/podman_ddns/ddns.md
Normal file
130
active/podman_ddns/ddns.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# DDNS for Route53
|
||||
|
||||
- [DDNS for Route53](#ddns-for-route53)
|
||||
- [Install](#install)
|
||||
- [As a Systemd Service](#as-a-systemd-service)
|
||||
- [Ansible Caddy Records](#ansible-caddy-records)
|
||||
- [Ansible Git Record](#ansible-git-record)
|
||||
- [Ansible Unifi External Records](#ansible-unifi-external-records)
|
||||
- [Ansible Hostname reeselink records](#ansible-hostname-reeselink-records)
|
||||
- [Development](#development)
|
||||
- [Testing](#testing)
|
||||
- [Building Container Image](#building-container-image)
|
||||
|
||||
This service will automatically keep ipv4 and ipv6 records updated in AWS Route53.
|
||||
|
||||
**NOTE**: This requires the aws cli to be installed on each node with
|
||||
credentials that can modify records in route53. See
|
||||
[aws_iam](/active/aws_iam/aws_iam.md) and
|
||||
[aws_cli](/active/aws_cli/aws_cli.md)
|
||||
|
||||
## Install
|
||||
|
||||
### As a Systemd Service
|
||||
|
||||
You need two files:
|
||||
|
||||
1. secrets/vars.yaml (with aws credentials)
|
||||
2. secrets/records.yaml (with AWS records)
|
||||
|
||||
`secrets/vars.yaml` example:
|
||||
|
||||
```yaml
|
||||
aws:
|
||||
access_key_id: key_here
|
||||
secret_access_key: secret_here
|
||||
```
|
||||
|
||||
`secrets/records.yaml` example:
|
||||
|
||||
```yaml
|
||||
records:
|
||||
- record: some.domain.com
|
||||
hosted_zone_id: ABC123456789
|
||||
- record: someother.domain.com
|
||||
hosted_zone_id: ABC123456789
|
||||
```
|
||||
|
||||
Then you can install the ddns service with something like
|
||||
|
||||
```bash
|
||||
ansible-playbook \
|
||||
# specify your inventory
|
||||
-i ansible/inventory.yaml \
|
||||
# -l limits to a particular host
|
||||
-l 3dserver \
|
||||
active/podman_ddns/install_ddns.yaml \
|
||||
# -e brings in our secrets/records.yaml
|
||||
-e "@active/podman_ddns/secrets/records.yaml"
|
||||
```
|
||||
|
||||
See ansible playbook [install_ddns.yaml](/install_ddns.yaml)
|
||||
|
||||
#### Ansible Caddy Records
|
||||
|
||||
```bash
|
||||
ansible-playbook \
|
||||
-i ansible/inventory.yaml \
|
||||
-l 3dserver \
|
||||
active/podman_ddns/install_ddns.yaml \
|
||||
-e "@active/podman_ddns/secrets/caddy_records.yaml"
|
||||
```
|
||||
|
||||
#### Ansible Git Record
|
||||
|
||||
```bash
|
||||
ansible-playbook \
|
||||
-i ansible/inventory.yaml \
|
||||
-l podman \
|
||||
active/podman_ddns/install_ddns.yaml \
|
||||
-e "@active/podman_ddns/secrets/git_record.yaml"
|
||||
```
|
||||
|
||||
#### Ansible Unifi External Records
|
||||
|
||||
```bash
|
||||
ansible-playbook \
|
||||
-i ansible/inventory.yaml \
|
||||
-l unifi-external \
|
||||
active/podman_ddns/install_ddns.yaml \
|
||||
-e "@active/podman_ddns/secrets/unifi_external_record.yaml"
|
||||
```
|
||||
|
||||
#### Ansible Hostname reeselink records
|
||||
|
||||
```bash
|
||||
export PLAYBOOK_PATH=active/podman_ddns
|
||||
ansible-playbook \
|
||||
-i ansible/inventory.yaml \
|
||||
${PLAYBOOK_PATH}/install_ddns.yaml \
|
||||
-e "@${PLAYBOOK_PATH}/secrets/hostname_reeselink_record.yaml"
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
export ROUTE53_RECORD=test-ddns.reeseapps.com
|
||||
export HOSTED_ZONE_ID=$(cat secrets/secret_vars.yaml | yq -r '.reeseapps_zone_id')
|
||||
uv run update.py
|
||||
```
|
||||
|
||||
### Building Container Image
|
||||
|
||||
```bash
|
||||
# Build
|
||||
podman build -t gitea.reeseapps.com/services/ddns:latest -f ./Containerfile
|
||||
podman push gitea.reeseapps.com/services/ddns:latest
|
||||
|
||||
# Run
|
||||
export ROUTE53_RECORD=test-ddns.reeseapps.com
|
||||
export HOSTED_ZONE_ID=$(cat secrets/secret_vars.yaml | yq -r '.reeseapps_zone_id')
|
||||
podman run \
|
||||
-e ROUTE53_RECORD=$ROUTE53_RECORD \
|
||||
-e HOSTED_ZONE_ID=$HOSTED_ZONE_ID \
|
||||
-e AWS_PROFILE=prod \
|
||||
-v $HOME/.aws:/root/.aws:Z \
|
||||
-it --rm \
|
||||
gitea.reeseapps.com/services/ddns:latest
|
||||
```
|
||||
8
active/podman_ddns/ddns.network
Normal file
8
active/podman_ddns/ddns.network
Normal file
@@ -0,0 +1,8 @@
|
||||
[Unit]
|
||||
Description=DDNS
|
||||
|
||||
[Network]
|
||||
IPv6=true
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
11
active/podman_ddns/ddns.timer
Normal file
11
active/podman_ddns/ddns.timer
Normal file
@@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=Run ddns.{{ item.record }}.service every hour
|
||||
|
||||
[Timer]
|
||||
OnCalendar=hourly
|
||||
AccuracySec=10min
|
||||
Persistent=true
|
||||
Unit=ddns.{{ item.record }}.service
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
45
active/podman_ddns/install_ddns.yaml
Normal file
45
active/podman_ddns/install_ddns.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
- name: Create DDNS Service
|
||||
hosts: all
|
||||
vars_files:
|
||||
- secrets/vars.yaml
|
||||
tasks:
|
||||
- name: Create /etc/ddns dir
|
||||
ansible.builtin.file:
|
||||
path: /etc/ddns
|
||||
state: directory
|
||||
mode: '0755'
|
||||
- name: Copy ddns.network
|
||||
template:
|
||||
src: ddns.network
|
||||
dest: /etc/containers/systemd/ddns.network
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
- name: Template DDNS Container Services
|
||||
template:
|
||||
src: ddns.container
|
||||
dest: /etc/containers/systemd/ddns.{{ item.record }}.container
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
loop: "{{ records }}"
|
||||
- name: Template DDNS Container Timers
|
||||
template:
|
||||
src: ddns.timer
|
||||
dest: /etc/systemd/system/ddns.{{ item.record }}.timer
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
loop: "{{ records }}"
|
||||
- name: Reload ddns timer
|
||||
ansible.builtin.systemd_service:
|
||||
state: restarted
|
||||
name: ddns.{{ item.record }}.timer
|
||||
enabled: true
|
||||
daemon_reload: true
|
||||
loop: "{{ records }}"
|
||||
- name: Run ddns service
|
||||
ansible.builtin.systemd_service:
|
||||
state: restarted
|
||||
name: ddns.{{ item.record }}.service
|
||||
loop: "{{ records }}"
|
||||
10
active/podman_ddns/pyproject.toml
Normal file
10
active/podman_ddns/pyproject.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[project]
|
||||
name = "ddns"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"boto3>=1.37.30",
|
||||
"pytest>=8.3.5",
|
||||
]
|
||||
32
active/podman_ddns/test_update.py
Normal file
32
active/podman_ddns/test_update.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import re
|
||||
|
||||
from update import get_ipv4, get_ipv6
|
||||
|
||||
regex_match_ipv4 = (
|
||||
r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
|
||||
)
|
||||
|
||||
regex_match_ipv6 = (
|
||||
r"(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:)"
|
||||
r"{1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:)"
|
||||
r"{1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]"
|
||||
r"{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:"
|
||||
r"[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4})"
|
||||
r"{0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9])"
|
||||
r"{0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}"
|
||||
r"(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))"
|
||||
)
|
||||
|
||||
def test_get_ipv4():
|
||||
ip = get_ipv4()
|
||||
assert re.match(
|
||||
regex_match_ipv4,
|
||||
ip
|
||||
)
|
||||
|
||||
def test_get_ipv6():
|
||||
ip = get_ipv6()
|
||||
assert re.match(
|
||||
regex_match_ipv6,
|
||||
ip
|
||||
)
|
||||
127
active/podman_ddns/update.py
Normal file
127
active/podman_ddns/update.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""
|
||||
export HOSTED_ZONE_ID=<aws hosted zone ID>
|
||||
export ROUTE53_RECORD=something.mydomain.com
|
||||
"""
|
||||
|
||||
import boto3
|
||||
import os
|
||||
import logging
|
||||
import subprocess
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s.%(msecs)03d %(levelname)s %(module)s - %(funcName)s: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S',
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
HOSTED_ZONE_ID = os.getenv("HOSTED_ZONE_ID")
|
||||
ROUTE53_RECORD = os.getenv("ROUTE53_RECORD")
|
||||
SKIP_IPV4 = os.getenv("SKIP_IPV4", "false").lower() == "true"
|
||||
SKIP_IPV6 = os.getenv("SKIP_IPV6", "false").lower() == "true"
|
||||
|
||||
|
||||
def get_ipv4() -> str:
|
||||
result = subprocess.run(["curl", "-4", "ifconfig.me"], capture_output=True)
|
||||
return result.stdout.decode()
|
||||
|
||||
def get_ipv6() -> str:
|
||||
result = subprocess.run(["curl", "-6", "ifconfig.me"], capture_output=True)
|
||||
return result.stdout.decode()
|
||||
|
||||
def update_ipv4(hosted_zone_id: str, record: str, public_ipv4: str):
|
||||
client = boto3.client("route53")
|
||||
try:
|
||||
logger.info("Calling upsert for ipv4.")
|
||||
client.change_resource_record_sets(
|
||||
HostedZoneId=hosted_zone_id,
|
||||
ChangeBatch={
|
||||
"Comment": "Update Public Addresses",
|
||||
"Changes": [
|
||||
{
|
||||
"Action": "UPSERT",
|
||||
"ResourceRecordSet": {
|
||||
"Name": f"{record}",
|
||||
"Type": "A",
|
||||
"TTL": 300,
|
||||
"ResourceRecords": [
|
||||
{
|
||||
"Value": public_ipv4
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
logger.info(f"Successfully updated ipv4 for {record}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating ipv4 for {record}.")
|
||||
raise e
|
||||
|
||||
def update_ipv6(hosted_zone_id: str, record: str, public_ipv6: str):
|
||||
client = boto3.client("route53")
|
||||
try:
|
||||
logger.info("Calling upsert for ipv6.")
|
||||
client.change_resource_record_sets(
|
||||
HostedZoneId=hosted_zone_id,
|
||||
ChangeBatch={
|
||||
"Comment": "Update Public Addresses",
|
||||
"Changes": [
|
||||
{
|
||||
"Action": "UPSERT",
|
||||
"ResourceRecordSet": {
|
||||
"Name": f"{record}",
|
||||
"Type": "AAAA",
|
||||
"TTL": 300,
|
||||
"ResourceRecords": [
|
||||
{
|
||||
"Value": public_ipv6
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
logger.info(f"Successfully updated ipv6 for {record}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating ipv6 for {record}.")
|
||||
raise e
|
||||
|
||||
def main():
|
||||
if not HOSTED_ZONE_ID:
|
||||
logger.error("HOSTED_ZONE_ID env var not found!")
|
||||
exit(1)
|
||||
|
||||
if not ROUTE53_RECORD:
|
||||
logger.error("ROUTE53_RECORD env var not found!")
|
||||
exit(1)
|
||||
|
||||
logger.info(f"Attempting to update {ROUTE53_RECORD} from {HOSTED_ZONE_ID}.")
|
||||
|
||||
if SKIP_IPV4:
|
||||
logger.warning("Skipping IPv4.")
|
||||
else:
|
||||
logger.info("Getting IPv4 address from ifconfig.me")
|
||||
public_ipv4 = get_ipv4()
|
||||
if not public_ipv4:
|
||||
logger.error("Public IPv4 not found.")
|
||||
exit(1)
|
||||
logger.info(f"Public IPv4 is {public_ipv4}")
|
||||
update_ipv4(hosted_zone_id=HOSTED_ZONE_ID, record=ROUTE53_RECORD, public_ipv4=public_ipv4)
|
||||
|
||||
if SKIP_IPV6:
|
||||
logger.warning("Skipping IPv6")
|
||||
else:
|
||||
logger.info("Getting IPv6 address from ifconfig.me")
|
||||
public_ipv6 = get_ipv6()
|
||||
if not public_ipv6:
|
||||
logger.error("Public IPv6 not found.")
|
||||
exit(1)
|
||||
logger.info(f"Public IPv6 is {public_ipv6}")
|
||||
update_ipv6(hosted_zone_id=HOSTED_ZONE_ID, record=ROUTE53_RECORD, public_ipv6=public_ipv6)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
197
active/podman_ddns/uv.lock
generated
Normal file
197
active/podman_ddns/uv.lock
generated
Normal file
@@ -0,0 +1,197 @@
|
||||
version = 1
|
||||
requires-python = ">=3.10"
|
||||
|
||||
[[package]]
|
||||
name = "boto3"
|
||||
version = "1.37.30"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "botocore" },
|
||||
{ name = "jmespath" },
|
||||
{ name = "s3transfer" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f0/4b/fc19e5b08f5871ebc92a5bb8919133245bf63ab4e985b91e00ff78cd0905/boto3-1.37.30.tar.gz", hash = "sha256:beea13db5a5f5eaacecfa905cd1e4e933c13802f776198264eef229d6dffcc42", size = 111380 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/28/3a/072a196ee038e859a6bdd52e374533c722e8b943c9a52c673dab70976d98/boto3-1.37.30-py3-none-any.whl", hash = "sha256:c75d78013eb43b354662cbd5f30bf537ab06641d3ed37aaad6fcf55a529d2991", size = 139560 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "botocore"
|
||||
version = "1.37.30"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jmespath" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/49/e7/29af47eb173faaeef3daabcc3e94bd8b6c1d87e1ba8eef1c6a18827b9cee/botocore-1.37.30.tar.gz", hash = "sha256:2f43b61e0231abbb4fbe8917acb1af98cb83dbab8c264c0d1f5ca0f16fdbf219", size = 13810655 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/85/cef0fdbd17f09cddc97c6b3182b099e2583ca77caec76f8a09f76794266e/botocore-1.37.30-py3-none-any.whl", hash = "sha256:d8ca899962d2079acd52483581f607322513910337a69bdae697766404b85b7d", size = 13476760 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ddns"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "boto3" },
|
||||
{ name = "pytest" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "boto3", specifier = ">=1.37.30" },
|
||||
{ name = "pytest", specifier = ">=8.3.5" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.2.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jmespath"
|
||||
version = "1.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "24.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.3.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
||||
{ name = "iniconfig" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pluggy" },
|
||||
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.9.0.post0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "s3transfer"
|
||||
version = "0.11.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "botocore" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0f/ec/aa1a215e5c126fe5decbee2e107468f51d9ce190b9763cb649f76bb45938/s3transfer-0.11.4.tar.gz", hash = "sha256:559f161658e1cf0a911f45940552c696735f5c74e64362e515f333ebed87d679", size = 148419 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/86/62/8d3fc3ec6640161a5649b2cddbbf2b9fa39c92541225b33f117c37c5a2eb/s3transfer-0.11.4-py3-none-any.whl", hash = "sha256:ac265fa68318763a03bf2dc4f39d5cbd6a9e178d81cc9483ad27da33637e320d", size = 84412 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 },
|
||||
]
|
||||
Reference in New Issue
Block a user