zboxapi 0.0.5__tar.gz → 0.0.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zboxapi-0.0.7/PKG-INFO +135 -0
- zboxapi-0.0.7/README.md +118 -0
- {zboxapi-0.0.5 → zboxapi-0.0.7}/pyproject.toml +1 -1
- zboxapi-0.0.7/src/zboxapi/__init__.py +1 -0
- zboxapi-0.0.7/src/zboxapi/dns.py +222 -0
- zboxapi-0.0.7/src/zboxapi/main.py +83 -0
- zboxapi-0.0.7/src/zboxapi/vlan.py +709 -0
- zboxapi-0.0.5/PKG-INFO +0 -51
- zboxapi-0.0.5/README.md +0 -34
- zboxapi-0.0.5/src/zboxapi/__init__.py +0 -1
- zboxapi-0.0.5/src/zboxapi/main.py +0 -281
zboxapi-0.0.7/PKG-INFO
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: zboxapi
|
|
3
|
+
Version: 0.0.7
|
|
4
|
+
Summary:
|
|
5
|
+
Author: Kelby Valenti
|
|
6
|
+
Author-email: kelby.valenti@gmail.com
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Requires-Dist: fastapi (==0.111.0)
|
|
13
|
+
Requires-Dist: ipython (>=8.24.0,<9.0.0)
|
|
14
|
+
Requires-Dist: uvicorn (==0.29.0)
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# zBoxApi
|
|
18
|
+
|
|
19
|
+
zPodFactory zBox Api
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- **DNS Management**: Manage DNS records in `/etc/hosts` with automatic dnsmasq integration
|
|
24
|
+
- **VLAN Management**: Manage VLAN interfaces with automatic network configuration
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
Complete the following steps to set up zBox Api:
|
|
29
|
+
|
|
30
|
+
1. Install pipx
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Install and configure pipx
|
|
34
|
+
apt update
|
|
35
|
+
apt install -y pipx
|
|
36
|
+
pipx ensurepath
|
|
37
|
+
|
|
38
|
+
# Reload your profile
|
|
39
|
+
source ~/.zshrc
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
1. Install zBoxApi:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pipx install zboxapi
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
1. Set up and start zboxapi.service
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
cp zboxapi.service /etc/systemd/system
|
|
52
|
+
systemctl daemon-reload
|
|
53
|
+
systemctl enable zboxapi.service
|
|
54
|
+
systemctl start zboxapi.service
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Note**: The service runs on `127.0.0.1:8000` and requires root privileges for network configuration operations.
|
|
58
|
+
|
|
59
|
+
## Configuration
|
|
60
|
+
|
|
61
|
+
### VLAN Management
|
|
62
|
+
|
|
63
|
+
For VLAN management functionality, create a configuration file at `/etc/zboxapi.conf`:
|
|
64
|
+
|
|
65
|
+
```ini
|
|
66
|
+
[DEFAULT]
|
|
67
|
+
# Base interface name for VLAN management
|
|
68
|
+
interface = eth1
|
|
69
|
+
|
|
70
|
+
# MTU setting for VLAN interfaces
|
|
71
|
+
mtu = 1700
|
|
72
|
+
|
|
73
|
+
# System default VLANs that cannot be modified (comma-separated)
|
|
74
|
+
system_vlans_default = 10,20,30
|
|
75
|
+
|
|
76
|
+
# System zPod VLANs that cannot be modified (comma-separated)
|
|
77
|
+
system_vlans_zpod = 64,128,192
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
See [DOC_VLAN.md](DOC_VLAN.md) for detailed documentation on VLAN management features.
|
|
81
|
+
|
|
82
|
+
## API Usage
|
|
83
|
+
|
|
84
|
+
### Authentication
|
|
85
|
+
|
|
86
|
+
All API endpoints require authentication using the `access_token` header. The API key is the zPod password which is also the root password of the zbox VM. The password is automatically retrieved from VMware tools:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
curl -H "access_token: your_zpod_password" http://127.0.0.1:8000/dns
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Note**: The service runs on `127.0.0.1:8000` and requires root privileges for network configuration operations.
|
|
93
|
+
|
|
94
|
+
### DNS Management
|
|
95
|
+
|
|
96
|
+
Manage DNS records in `/etc/hosts`:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Add DNS record
|
|
100
|
+
curl -X POST "http://127.0.0.1:8000/dns" \
|
|
101
|
+
-H "access_token: your_zpod_password" \
|
|
102
|
+
-H "Content-Type: application/json" \
|
|
103
|
+
-d '{"ip": "192.168.1.100", "hostname": "example.com"}'
|
|
104
|
+
|
|
105
|
+
# List all DNS records
|
|
106
|
+
curl -X GET "http://127.0.0.1:8000/dns" \
|
|
107
|
+
-H "access_token: your_zpod_password"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
For complete DNS management documentation, see [DOC_DNS.md](DOC_DNS.md).
|
|
111
|
+
|
|
112
|
+
### VLAN Management
|
|
113
|
+
|
|
114
|
+
Manage VLAN interfaces:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Create VLAN interface
|
|
118
|
+
curl -X POST "http://127.0.0.1:8000/vlan" \
|
|
119
|
+
-H "access_token: your_zpod_password" \
|
|
120
|
+
-H "Content-Type: application/json" \
|
|
121
|
+
-d '{"vlan": 2000, "gateway": "192.168.42.129/25"}'
|
|
122
|
+
|
|
123
|
+
# List all VLAN interfaces
|
|
124
|
+
curl -X GET "http://127.0.0.1:8000/vlan" \
|
|
125
|
+
-H "access_token: your_zpod_password"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
For complete VLAN management documentation, see [DOC_VLAN.md](DOC_VLAN.md).
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
## Documentation
|
|
132
|
+
|
|
133
|
+
- [DOC_DNS.md](DOC_DNS.md) - Complete guide to DNS management features
|
|
134
|
+
- [DOC_VLAN.md](DOC_VLAN.md) - Complete guide to VLAN management features
|
|
135
|
+
|
zboxapi-0.0.7/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# zBoxApi
|
|
2
|
+
|
|
3
|
+
zPodFactory zBox Api
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **DNS Management**: Manage DNS records in `/etc/hosts` with automatic dnsmasq integration
|
|
8
|
+
- **VLAN Management**: Manage VLAN interfaces with automatic network configuration
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
Complete the following steps to set up zBox Api:
|
|
13
|
+
|
|
14
|
+
1. Install pipx
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Install and configure pipx
|
|
18
|
+
apt update
|
|
19
|
+
apt install -y pipx
|
|
20
|
+
pipx ensurepath
|
|
21
|
+
|
|
22
|
+
# Reload your profile
|
|
23
|
+
source ~/.zshrc
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
1. Install zBoxApi:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pipx install zboxapi
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
1. Set up and start zboxapi.service
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
cp zboxapi.service /etc/systemd/system
|
|
36
|
+
systemctl daemon-reload
|
|
37
|
+
systemctl enable zboxapi.service
|
|
38
|
+
systemctl start zboxapi.service
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Note**: The service runs on `127.0.0.1:8000` and requires root privileges for network configuration operations.
|
|
42
|
+
|
|
43
|
+
## Configuration
|
|
44
|
+
|
|
45
|
+
### VLAN Management
|
|
46
|
+
|
|
47
|
+
For VLAN management functionality, create a configuration file at `/etc/zboxapi.conf`:
|
|
48
|
+
|
|
49
|
+
```ini
|
|
50
|
+
[DEFAULT]
|
|
51
|
+
# Base interface name for VLAN management
|
|
52
|
+
interface = eth1
|
|
53
|
+
|
|
54
|
+
# MTU setting for VLAN interfaces
|
|
55
|
+
mtu = 1700
|
|
56
|
+
|
|
57
|
+
# System default VLANs that cannot be modified (comma-separated)
|
|
58
|
+
system_vlans_default = 10,20,30
|
|
59
|
+
|
|
60
|
+
# System zPod VLANs that cannot be modified (comma-separated)
|
|
61
|
+
system_vlans_zpod = 64,128,192
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
See [DOC_VLAN.md](DOC_VLAN.md) for detailed documentation on VLAN management features.
|
|
65
|
+
|
|
66
|
+
## API Usage
|
|
67
|
+
|
|
68
|
+
### Authentication
|
|
69
|
+
|
|
70
|
+
All API endpoints require authentication using the `access_token` header. The API key is the zPod password which is also the root password of the zbox VM. The password is automatically retrieved from VMware tools:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
curl -H "access_token: your_zpod_password" http://127.0.0.1:8000/dns
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Note**: The service runs on `127.0.0.1:8000` and requires root privileges for network configuration operations.
|
|
77
|
+
|
|
78
|
+
### DNS Management
|
|
79
|
+
|
|
80
|
+
Manage DNS records in `/etc/hosts`:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Add DNS record
|
|
84
|
+
curl -X POST "http://127.0.0.1:8000/dns" \
|
|
85
|
+
-H "access_token: your_zpod_password" \
|
|
86
|
+
-H "Content-Type: application/json" \
|
|
87
|
+
-d '{"ip": "192.168.1.100", "hostname": "example.com"}'
|
|
88
|
+
|
|
89
|
+
# List all DNS records
|
|
90
|
+
curl -X GET "http://127.0.0.1:8000/dns" \
|
|
91
|
+
-H "access_token: your_zpod_password"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
For complete DNS management documentation, see [DOC_DNS.md](DOC_DNS.md).
|
|
95
|
+
|
|
96
|
+
### VLAN Management
|
|
97
|
+
|
|
98
|
+
Manage VLAN interfaces:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Create VLAN interface
|
|
102
|
+
curl -X POST "http://127.0.0.1:8000/vlan" \
|
|
103
|
+
-H "access_token: your_zpod_password" \
|
|
104
|
+
-H "Content-Type: application/json" \
|
|
105
|
+
-d '{"vlan": 2000, "gateway": "192.168.42.129/25"}'
|
|
106
|
+
|
|
107
|
+
# List all VLAN interfaces
|
|
108
|
+
curl -X GET "http://127.0.0.1:8000/vlan" \
|
|
109
|
+
-H "access_token: your_zpod_password"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
For complete VLAN management documentation, see [DOC_VLAN.md](DOC_VLAN.md).
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
## Documentation
|
|
116
|
+
|
|
117
|
+
- [DOC_DNS.md](DOC_DNS.md) - Complete guide to DNS management features
|
|
118
|
+
- [DOC_VLAN.md](DOC_VLAN.md) - Complete guide to VLAN management features
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.7"
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import fcntl
|
|
3
|
+
import re
|
|
4
|
+
import socket
|
|
5
|
+
import subprocess
|
|
6
|
+
import time
|
|
7
|
+
from ipaddress import IPv4Address
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import IO, Annotated
|
|
10
|
+
|
|
11
|
+
from fastapi import APIRouter, HTTPException, status
|
|
12
|
+
from pydantic import AfterValidator, BaseModel
|
|
13
|
+
from pydantic_core import PydanticCustomError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@contextlib.contextmanager
|
|
17
|
+
def get_hosts_file_object():
|
|
18
|
+
"""Context manager for safely handling /etc/hosts file"""
|
|
19
|
+
pfile = Path("/etc/hosts")
|
|
20
|
+
if not pfile.is_file():
|
|
21
|
+
pfile.write_text("")
|
|
22
|
+
|
|
23
|
+
while True:
|
|
24
|
+
try:
|
|
25
|
+
file_handle = pfile.open("r+")
|
|
26
|
+
fcntl.flock(file_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
27
|
+
break
|
|
28
|
+
except IOError: # noqa: UP024
|
|
29
|
+
# File is locked, wait for a while and try again
|
|
30
|
+
time.sleep(0.1)
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
yield file_handle
|
|
34
|
+
finally:
|
|
35
|
+
fcntl.flock(file_handle, fcntl.LOCK_UN)
|
|
36
|
+
file_handle.close()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_hosts_lines(hosts_fo: IO) -> list[dict[str, str]]:
|
|
40
|
+
"""Parse hosts file and return list of IP-hostname mappings"""
|
|
41
|
+
lines = []
|
|
42
|
+
for line in hosts_fo.read().splitlines():
|
|
43
|
+
line = line.strip()
|
|
44
|
+
if not line or line.startswith("#"):
|
|
45
|
+
continue
|
|
46
|
+
line_ip, *line_hostnames = line.split()
|
|
47
|
+
lines.extend(
|
|
48
|
+
{"ip": IPv4Address(line_ip), "hostname": line_hostname}
|
|
49
|
+
for line_hostname in line_hostnames
|
|
50
|
+
)
|
|
51
|
+
return sorted(lines, key=sort_hosts_lines)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def filter_hosts_file(
|
|
55
|
+
lines: list[dict[str, str]],
|
|
56
|
+
ip: IPv4Address | None = None,
|
|
57
|
+
hostname: str | None = None,
|
|
58
|
+
):
|
|
59
|
+
"""Filter hosts file lines by IP and/or hostname"""
|
|
60
|
+
return [
|
|
61
|
+
line
|
|
62
|
+
for line in lines
|
|
63
|
+
if (not ip or ip == line["ip"])
|
|
64
|
+
and (not hostname or hostname == line["hostname"])
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def write_hosts_file(hosts_fo: IO, lines: list[dict[str, str]]):
|
|
69
|
+
"""Write hosts file lines back to file"""
|
|
70
|
+
lines.sort(key=sort_hosts_lines)
|
|
71
|
+
hosts_fo.seek(0)
|
|
72
|
+
hosts_fo.truncate()
|
|
73
|
+
hosts_fo.writelines([f"{line['ip']}\t{line['hostname']}\n" for line in lines])
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def dnsmasq_sighup():
|
|
77
|
+
"""Send SIGHUP to dnsmasq to reload configuration"""
|
|
78
|
+
print("Send SIGHUP to dnsmasq...")
|
|
79
|
+
subprocess.call(["pkill", "-SIGHUP", "dnsmasq"])
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def sort_hosts_lines(item: dict):
|
|
83
|
+
"""Sort list by loopback first, ip second, hostname third"""
|
|
84
|
+
return (
|
|
85
|
+
not item["ip"].is_loopback,
|
|
86
|
+
socket.inet_aton(str(item["ip"])),
|
|
87
|
+
item["hostname"],
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def RecordNotFound(ip, hostname):
|
|
92
|
+
"""Return HTTPException for DNS record not found"""
|
|
93
|
+
return HTTPException(
|
|
94
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
95
|
+
detail=f"DNS record not found: ip={ip}, hostname={hostname}",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def RecordAlreadyPresent(ip, hostname):
|
|
100
|
+
"""Return HTTPException for DNS record already present"""
|
|
101
|
+
return HTTPException(
|
|
102
|
+
status_code=status.HTTP_406_NOT_ACCEPTABLE,
|
|
103
|
+
detail=f"DNS record already present: ip={ip}, hostname={hostname}",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def validate_hostname(value: str):
|
|
108
|
+
"""Validate hostname format"""
|
|
109
|
+
if not 1 <= len(value) < 64:
|
|
110
|
+
raise PydanticCustomError("value_error", "Invalid hostname length")
|
|
111
|
+
|
|
112
|
+
# Define pattern of DNS label
|
|
113
|
+
# Can begin and end with a number or letter only
|
|
114
|
+
# Can contain hyphens, a-z, A-Z, 0-9
|
|
115
|
+
# 1 - 63 chars allowed
|
|
116
|
+
hostname_re = re.compile(r"^[a-z0-9]([a-z-0-9-]{0,61}[a-z0-9])?$", re.IGNORECASE)
|
|
117
|
+
|
|
118
|
+
# Check that all labels match that pattern.
|
|
119
|
+
if not hostname_re.match(value):
|
|
120
|
+
raise PydanticCustomError("value_error", "Invalid hostname")
|
|
121
|
+
return value
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
HOSTNAME = Annotated[str, AfterValidator(validate_hostname)]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class DnsCreate(BaseModel):
|
|
128
|
+
"""Model for creating DNS records"""
|
|
129
|
+
|
|
130
|
+
ip: IPv4Address
|
|
131
|
+
hostname: HOSTNAME
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class DnsUpdate(BaseModel):
|
|
135
|
+
"""Model for updating DNS records"""
|
|
136
|
+
|
|
137
|
+
ip: IPv4Address
|
|
138
|
+
hostname: HOSTNAME
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class DnsView(BaseModel):
|
|
142
|
+
"""Model for viewing DNS records"""
|
|
143
|
+
|
|
144
|
+
ip: IPv4Address
|
|
145
|
+
hostname: str
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# API Router
|
|
149
|
+
dns_router = APIRouter(prefix="/dns", tags=["dns"])
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@dns_router.get("", response_model=list[DnsView])
|
|
153
|
+
def dns_get_all() -> list[DnsView]:
|
|
154
|
+
"""Get all DNS records"""
|
|
155
|
+
with get_hosts_file_object() as hosts_fo:
|
|
156
|
+
hosts_lines = get_hosts_lines(hosts_fo)
|
|
157
|
+
return hosts_lines
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@dns_router.get("/{ip}/{hostname}", response_model=DnsView)
|
|
161
|
+
def dns_get(
|
|
162
|
+
ip: IPv4Address,
|
|
163
|
+
hostname: HOSTNAME,
|
|
164
|
+
) -> DnsView:
|
|
165
|
+
"""Get specific DNS record"""
|
|
166
|
+
with get_hosts_file_object() as hosts_fo:
|
|
167
|
+
hosts_lines = get_hosts_lines(hosts_fo)
|
|
168
|
+
hosts_lines = filter_hosts_file(hosts_lines, ip, hostname)
|
|
169
|
+
if not hosts_lines:
|
|
170
|
+
raise RecordNotFound(ip, hostname)
|
|
171
|
+
return hosts_lines[0]
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@dns_router.post("", response_model=list[DnsView])
|
|
175
|
+
def dns_add(
|
|
176
|
+
dns_in: DnsCreate,
|
|
177
|
+
) -> list[DnsView]:
|
|
178
|
+
"""Add DNS record"""
|
|
179
|
+
with get_hosts_file_object() as hosts_fo:
|
|
180
|
+
hosts_lines = get_hosts_lines(hosts_fo)
|
|
181
|
+
if filter_hosts_file(hosts_lines, dns_in.ip, dns_in.hostname):
|
|
182
|
+
raise RecordAlreadyPresent(dns_in.ip, dns_in.hostname)
|
|
183
|
+
hosts_lines.append({"ip": dns_in.ip, "hostname": dns_in.hostname})
|
|
184
|
+
write_hosts_file(hosts_fo, hosts_lines)
|
|
185
|
+
dnsmasq_sighup()
|
|
186
|
+
return hosts_lines
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@dns_router.put("/{ip}/{hostname}", response_model=list[DnsView])
|
|
190
|
+
def dns_update(
|
|
191
|
+
ip: IPv4Address,
|
|
192
|
+
hostname: HOSTNAME,
|
|
193
|
+
dns_in: DnsUpdate,
|
|
194
|
+
) -> list[DnsView]:
|
|
195
|
+
"""Update DNS record"""
|
|
196
|
+
with get_hosts_file_object() as hosts_fo:
|
|
197
|
+
hosts_lines = get_hosts_lines(hosts_fo)
|
|
198
|
+
key = {"ip": ip, "hostname": hostname}
|
|
199
|
+
if key not in hosts_lines:
|
|
200
|
+
raise RecordNotFound(ip, hostname)
|
|
201
|
+
ix = hosts_lines.index(key)
|
|
202
|
+
hosts_lines[ix] = {"ip": dns_in.ip, "hostname": dns_in.hostname}
|
|
203
|
+
write_hosts_file(hosts_fo, hosts_lines)
|
|
204
|
+
dnsmasq_sighup()
|
|
205
|
+
return hosts_lines
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@dns_router.delete("/{ip}/{hostname}", response_model=list[DnsView])
|
|
209
|
+
def dns_delete(
|
|
210
|
+
ip: IPv4Address,
|
|
211
|
+
hostname: HOSTNAME,
|
|
212
|
+
) -> list[DnsView]:
|
|
213
|
+
"""Delete DNS record"""
|
|
214
|
+
with get_hosts_file_object() as hosts_fo:
|
|
215
|
+
hosts_lines = get_hosts_lines(hosts_fo)
|
|
216
|
+
key = {"ip": ip, "hostname": hostname}
|
|
217
|
+
if key not in hosts_lines:
|
|
218
|
+
raise RecordNotFound(ip, hostname)
|
|
219
|
+
hosts_lines.remove(key)
|
|
220
|
+
write_hosts_file(hosts_fo, hosts_lines)
|
|
221
|
+
dnsmasq_sighup()
|
|
222
|
+
return hosts_lines
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import subprocess
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import uvicorn
|
|
7
|
+
from fastapi import Depends, FastAPI, HTTPException, Security, status
|
|
8
|
+
from fastapi.routing import APIRoute
|
|
9
|
+
from fastapi.security.api_key import APIKey, APIKeyHeader
|
|
10
|
+
|
|
11
|
+
from zboxapi import __version__
|
|
12
|
+
from zboxapi.dns import dns_router
|
|
13
|
+
from zboxapi.vlan import vlan_router
|
|
14
|
+
|
|
15
|
+
api_key_header = APIKeyHeader(name="access_token", auto_error=False)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def validate_api_key(api_key: Annotated[APIKey, Security(api_key_header)]):
|
|
19
|
+
"""Validate API key for authentication"""
|
|
20
|
+
if api_key != ZPOD_PASSWORD:
|
|
21
|
+
raise HTTPException(
|
|
22
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
23
|
+
detail="Invalid access_token",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_zpod_password():
|
|
28
|
+
"""Retrieve zpod password from VMware tools"""
|
|
29
|
+
ovfenv = subprocess.run(
|
|
30
|
+
["vmtoolsd", "--cmd", "info-get guestinfo.ovfenv"],
|
|
31
|
+
capture_output=True,
|
|
32
|
+
text=True,
|
|
33
|
+
)
|
|
34
|
+
pw_re = re.compile(r'<Property oe:key="guestinfo.password" oe:value="([^"]*)"/>')
|
|
35
|
+
if item := re.search(pw_re, ovfenv.stdout):
|
|
36
|
+
return item[1]
|
|
37
|
+
raise Exception("Unable to retrieve zpod password")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def simplify_operation_ids(api: FastAPI) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Update operation IDs so that generated API clients have simpler function
|
|
43
|
+
names.
|
|
44
|
+
"""
|
|
45
|
+
for route in api.routes:
|
|
46
|
+
if isinstance(route, APIRoute) and not route.operation_id:
|
|
47
|
+
tag = route.tags[0] if route.tags else "default"
|
|
48
|
+
route.operation_id = f"{tag}_{route.name}"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# Get zpod password for authentication
|
|
52
|
+
ZPOD_PASSWORD = get_zpod_password()
|
|
53
|
+
|
|
54
|
+
# Get root path from environment
|
|
55
|
+
zboxapi_root_path = os.getenv("ZBOXAPI_ROOT_PATH", None)
|
|
56
|
+
|
|
57
|
+
# Create FastAPI application
|
|
58
|
+
app = FastAPI(
|
|
59
|
+
title="zBox API",
|
|
60
|
+
root_path=zboxapi_root_path,
|
|
61
|
+
dependencies=[Depends(validate_api_key)],
|
|
62
|
+
version=__version__,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Include routers
|
|
66
|
+
app.include_router(dns_router)
|
|
67
|
+
app.include_router(vlan_router)
|
|
68
|
+
|
|
69
|
+
# Simplify operation IDs
|
|
70
|
+
simplify_operation_ids(app)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def launch():
|
|
74
|
+
"""Launch the FastAPI application with uvicorn"""
|
|
75
|
+
uvicorn.run(
|
|
76
|
+
app,
|
|
77
|
+
host="127.0.0.1",
|
|
78
|
+
port=8000,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
if __name__ == "__main__":
|
|
83
|
+
launch()
|