Skip to content

Commit 1385138

Browse files
authored
Webdav url (#52)
## Summary update webdav support to improve etag handling ## Changes - ... ## Test Plan - [x] Tests pass locally - [x] Manual testing completed ## Related Issues Fixes #
1 parent f431042 commit 1385138

12 files changed

Lines changed: 802 additions & 243 deletions

File tree

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ repos:
1313
- id: debug-statements
1414
- repo: https://github.com/astral-sh/ruff-pre-commit
1515
# Ruff version.
16-
rev: v0.14.10
16+
rev: v0.15.7
1717
hooks:
1818
# Run the linter (reads all config from pyproject.toml).
1919
- id: ruff
2020
args: [--fix]
2121
# Run the formatter (reads line-length from pyproject.toml).
2222
- id: ruff-format
2323
- repo: https://github.com/gitleaks/gitleaks
24-
rev: v8.21.2
24+
rev: v8.30.1
2525
hooks:
2626
- id: gitleaks
2727
- repo: local
@@ -34,7 +34,7 @@ repos:
3434
pass_filenames: false
3535
files: (^pyproject\.toml$|^uv\.lock$)
3636
- repo: https://github.com/jackdewinter/pymarkdown
37-
rev: v0.9.35
37+
rev: v0.9.36
3838
hooks:
3939
- id: pymarkdown
4040
args:

README.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ WEBDAV_URL=https://webdav.example.com
131131
# WebDAV authentication
132132
WEBDAV_USERNAME=your-username
133133
WEBDAV_PASSWORD=your-password
134+
135+
# Disable TLS certificate verification (default: true)
136+
SSL_VERIFY=true
134137
```
135138

136139
All WebDAV credentials can also be provided via command-line options (`--webdav-url`, `--webdav-username`, `--webdav-password`), which override the environment variables.
@@ -155,6 +158,9 @@ AUTH_TRUST_PROXY_HEADERS=false
155158

156159
# Manifest scheduling (requires SCHEDULER_ENABLED=true)
157160
MANIFEST_DIR=/path/to/manifests
161+
162+
# S3-compatible storage (for urls_file references using s3:// URLs)
163+
S3_ENDPOINT_URL=https://minio.example.com:9000
158164
```
159165

160166
### Git CLI Mode
@@ -588,7 +594,7 @@ Top-level fields:
588594
- **name** (required): Component name.
589595
- **url**: Single URL to fetch.
590596
- **urls**: List of URLs to fetch.
591-
- **urls_file**: Path to a file containing URLs (one per line).
597+
- **urls_file**: Path to a file containing URLs (one per line). Supports local paths, `s3://bucket/key` URLs, and `http(s)://` WebDAV URLs.
592598
- Exactly one of `url`, `urls`, or `urls_file` must be specified.
593599
- **extensions**: Override extensions for this component.
594600
- **metadata**: Additional metadata merged with config-level metadata.
@@ -613,7 +619,7 @@ Top-level fields:
613619
- **url** (required): WebDAV server URL.
614620
- **path**: WebDAV directory path to scan recursively.
615621
- **urls**: List of specific WebDAV file paths to ingest.
616-
- **urls_file**: Path to a file containing WebDAV URLs (one per line).
622+
- **urls_file**: Path to a file containing WebDAV URLs (one per line). Supports local paths, `s3://bucket/key` URLs, and `http(s)://` WebDAV URLs (fetched using the same WebDAV credentials).
617623
- Exactly one of `path`, `urls`, or `urls_file` must be specified.
618624
- **username**: Override WebDAV username (resolved via Docker secrets or env vars).
619625
- **password**: Override WebDAV password (resolved via Docker secrets or env vars).
@@ -1165,6 +1171,10 @@ soliplex.agents/
11651171
│ │ ├── webdav.py # WebDAV API endpoints
11661172
│ │ ├── web.py # Web API endpoints
11671173
│ │ └── manifest.py # Manifest API endpoints
1174+
│ ├── common/ # Shared utilities
1175+
│ │ ├── urls_file.py # URL list reader (local, S3, WebDAV)
1176+
│ │ ├── s3.py # S3 object reader
1177+
│ │ └── config.py # MIME type detection, config helpers
11681178
│ ├── fs/ # Filesystem agent
11691179
│ │ ├── app.py # Core filesystem logic
11701180
│ │ └── cli.py # Filesystem CLI commands
@@ -1185,7 +1195,7 @@ soliplex.agents/
11851195
│ └── lib/
11861196
│ ├── templates/ # Issue rendering templates
11871197
│ └── utils.py # Utility functions
1188-
├── example-manifests/ # Example manifest YAML files
1198+
├── example-manifests/ # Example manifests (fs, scm, web, webdav, composite, delete-stale)
11891199
├── tests/ # Test suite
11901200
│ └── unit/
11911201
│ ├── test_server_*.py # Server API tests

src/soliplex/agents/client.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,13 @@ async def check_status(file_info: list[dict[str, Any]], source: str, delete_stal
250250
for x in file_info:
251251
x["uri"] = x.get("path", x.get("uri"))
252252

253-
status_dict = {x["uri"]: x["sha256"] for x in file_info}
253+
status_dict = {
254+
x["uri"]: {
255+
"sha256": x.get("sha256") or "",
256+
"etag": x.get("_etag") or "",
257+
}
258+
for x in file_info
259+
}
254260
uri_to_file = {x["uri"]: x for x in file_info}
255261

256262
url = _build_url("/source-status")

src/soliplex/agents/common/urls_file.py

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
"""Shared utility for reading URL list files from local paths or S3."""
1+
"""Shared utility for reading URL list files from local paths, S3, or WebDAV."""
22

33
import logging
4+
from io import BytesIO
45
from pathlib import Path
56

67
import aiofiles
@@ -12,6 +13,11 @@
1213
logger = logging.getLogger(__name__)
1314

1415

16+
def is_webdav_url(path: str) -> bool:
17+
"""Return True if *path* looks like an HTTP(S) URL."""
18+
return path.startswith("http://") or path.startswith("https://")
19+
20+
1521
def resolve_local_path(
1622
urls_file: str,
1723
base_dir: str | None = None,
@@ -42,25 +48,73 @@ def resolve_local_path(
4248
return urls_file
4349

4450

51+
def read_text_from_webdav(
52+
url: str,
53+
webdav_url: str | None = None,
54+
webdav_username: str | None = None,
55+
webdav_password: str | None = None,
56+
) -> str:
57+
"""Download a text file from a WebDAV server.
58+
59+
The full file URL is split into a base URL (scheme + host) and a
60+
path component. Authentication credentials fall back to the
61+
global settings when not provided explicitly. Client creation is
62+
delegated to :func:`~soliplex.agents.webdav.app.create_webdav_client`
63+
so that timeout, header, and TLS settings stay consistent.
64+
65+
Args:
66+
url: Full HTTP(S) URL to the file on the WebDAV server.
67+
webdav_url: Optional override for the WebDAV base URL.
68+
When *None* the base URL is derived from *url*.
69+
webdav_username: Optional WebDAV username.
70+
webdav_password: Optional WebDAV password.
71+
72+
Returns:
73+
The file contents decoded as UTF-8 text.
74+
"""
75+
from urllib.parse import urlparse
76+
77+
from soliplex.agents.webdav.app import create_webdav_client
78+
79+
parsed = urlparse(url)
80+
base_url = webdav_url or f"{parsed.scheme}://{parsed.netloc}"
81+
path = parsed.path
82+
83+
client = create_webdav_client(base_url, webdav_username, webdav_password)
84+
85+
buffer = BytesIO()
86+
client.download_fileobj(path, buffer)
87+
return buffer.getvalue().decode("utf-8")
88+
89+
4590
async def read_urls_file(
4691
urls_file: str,
4792
base_dir: str | None = None,
93+
webdav_url: str | None = None,
94+
webdav_username: str | None = None,
95+
webdav_password: str | None = None,
4896
) -> list[str]:
4997
"""Read a URL list file and return non-empty, stripped lines.
5098
51-
Supports S3 URLs (``s3://bucket/key``) and local filesystem paths.
52-
For local paths, relative paths are resolved against *base_dir*
53-
when provided (see :func:`resolve_local_path`).
99+
Supports S3 URLs (``s3://bucket/key``), WebDAV URLs
100+
(``http(s)://...``), and local filesystem paths. For local paths,
101+
relative paths are resolved against *base_dir* when provided (see
102+
:func:`resolve_local_path`).
54103
55104
Args:
56-
urls_file: Path or S3 URL to the URL list file.
105+
urls_file: Path, S3 URL, or WebDAV URL to the URL list file.
57106
base_dir: Optional directory for resolving relative local paths.
107+
webdav_url: Optional WebDAV base URL override (for WebDAV URLs).
108+
webdav_username: Optional WebDAV username (for WebDAV URLs).
109+
webdav_password: Optional WebDAV password (for WebDAV URLs).
58110
59111
Returns:
60112
List of non-empty, whitespace-stripped lines.
61113
"""
62114
if is_s3_url(urls_file):
63115
content = await read_text_from_s3(urls_file, settings.s3_endpoint_url)
116+
elif is_webdav_url(urls_file):
117+
content = read_text_from_webdav(urls_file, webdav_url, webdav_username, webdav_password)
64118
else:
65119
resolved = resolve_local_path(urls_file, base_dir)
66120
async with aiofiles.open(resolved) as f:

src/soliplex/agents/manifest/runner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ def collect_inventory_uris(result: dict[str, Any]) -> list[dict[str, str]]:
323323
items: list[dict[str, str]] = []
324324
for entry in result.get("inventory", []):
325325
uri = entry.get("uri") or entry.get("path")
326-
sha256 = entry.get("sha256", "")
326+
sha256 = entry.get("sha256") or ""
327327
if uri:
328328
items.append({"uri": uri, "sha256": sha256})
329329
return items

0 commit comments

Comments
 (0)